Merge "Avoid static import IntDef value to workaround for Java 8 javac issue" into sc-dev
diff --git a/Android.bp b/Android.bp
index f47ee20..6a47db1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -669,7 +669,6 @@
// TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
"gps_debug.conf",
"icu4j-platform-compat-config",
- "libcore-platform-compat-config",
"protolog.conf.json.gz",
"services-platform-compat-config",
"documents-ui-compat-config",
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 4c8ab93..5729385 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1021,6 +1021,41 @@
mJobId = jobId;
}
+ /**
+ * Creates a new Builder of JobInfo from an existing instance.
+ * @hide
+ */
+ public Builder(@NonNull JobInfo job) {
+ mJobId = job.getId();
+ mJobService = job.getService();
+ mExtras = job.getExtras();
+ mTransientExtras = job.getTransientExtras();
+ mClipData = job.getClipData();
+ mClipGrantFlags = job.getClipGrantFlags();
+ mPriority = job.getPriority();
+ mFlags = job.getFlags();
+ mConstraintFlags = job.getConstraintFlags();
+ mNetworkRequest = job.getRequiredNetwork();
+ mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+ mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
+ mTriggerContentUris = job.getTriggerContentUris() != null
+ ? new ArrayList<>(Arrays.asList(job.getTriggerContentUris())) : null;
+ mTriggerContentUpdateDelay = job.getTriggerContentUpdateDelay();
+ mTriggerContentMaxDelay = job.getTriggerContentMaxDelay();
+ mIsPersisted = job.isPersisted();
+ mMinLatencyMillis = job.getMinLatencyMillis();
+ mMaxExecutionDelayMillis = job.getMaxExecutionDelayMillis();
+ mIsPeriodic = job.isPeriodic();
+ mHasEarlyConstraint = job.hasEarlyConstraint();
+ mHasLateConstraint = job.hasLateConstraint();
+ mIntervalMillis = job.getIntervalMillis();
+ mFlexMillis = job.getFlexMillis();
+ mInitialBackoffMillis = job.getInitialBackoffMillis();
+ // mBackoffPolicySet isn't set but it's fine since this is copying from an already valid
+ // job.
+ mBackoffPolicy = job.getBackoffPolicy();
+ }
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Builder setPriority(int priority) {
@@ -1462,7 +1497,7 @@
* <ol>
* <li>Run as soon as possible</li>
* <li>Be less restricted during Doze and battery saver</li>
- * <li>Have the same network access as foreground services</li>
+ * <li>Bypass Doze, app standby, and battery saver network restrictions</li>
* <li>Be less likely to be killed than regular jobs</li>
* <li>Be subject to background location throttling</li>
* </ol>
@@ -1483,7 +1518,7 @@
*
* <p>
* Assuming all constraints remain satisfied (including ideal system load conditions),
- * expedited jobs are guaranteed to have a minimum allowed runtime of 1 minute. If your
+ * expedited jobs will have a maximum execution time of at least 1 minute. If your
* app has remaining expedited job quota, then the expedited job <i>may</i> potentially run
* longer until remaining quota is used up. Just like with regular jobs, quota is not
* consumed while the app is on top and visible to the user.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 325be1b..d94d638 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -891,7 +891,7 @@
}
// Only expedited jobs can replace expedited jobs.
- if (js.shouldTreatAsExpeditedJob()) {
+ if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) {
// Keep fg/bg user distinction.
if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) {
// Let any important bg user job replace a bg user expedited job.
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 a041f8c..8ac237e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2187,6 +2187,10 @@
*/
@VisibleForTesting
boolean isReadyToBeExecutedLocked(JobStatus job) {
+ return isReadyToBeExecutedLocked(job, true);
+ }
+
+ boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) {
final boolean jobReady = job.isReady();
if (DEBUG) {
@@ -2225,7 +2229,7 @@
}
final boolean jobPending = mPendingJobs.contains(job);
- final boolean jobActive = isCurrentlyActiveLocked(job);
+ final boolean jobActive = rejectActive && isCurrentlyActiveLocked(job);
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index e8bcbfb..790fae0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -151,6 +151,14 @@
/** The absolute maximum amount of time the job can run */
private long mMaxExecutionTimeMillis;
+ /**
+ * The stop reason for a pending cancel. If there's not pending cancel, then the value should be
+ * {@link JobParameters#STOP_REASON_UNDEFINED}.
+ */
+ private int mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
+ private int mPendingLegacyStopReason;
+ private String mPendingDebugStopReason;
+
// Debugging: reason this job was last stopped.
public String mStoppedReason;
@@ -328,6 +336,7 @@
mAvailable = false;
mStoppedReason = null;
mStoppedTime = 0;
+ job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
return true;
}
}
@@ -625,6 +634,19 @@
}
return;
}
+ if (mRunningJob.startedAsExpeditedJob
+ && stopReasonCode == JobParameters.STOP_REASON_QUOTA) {
+ // EJs should be able to run for at least the min upper limit regardless of quota.
+ final long earliestStopTimeElapsed =
+ mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (nowElapsed < earliestStopTimeElapsed) {
+ mPendingStopReason = stopReasonCode;
+ mPendingLegacyStopReason = legacyStopReason;
+ mPendingDebugStopReason = debugReason;
+ return;
+ }
+ }
mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason);
if (legacyStopReason == JobParameters.REASON_PREEMPT) {
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
@@ -777,6 +799,23 @@
closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
break;
case VERB_EXECUTING:
+ if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+ if (mService.isReadyToBeExecutedLocked(mRunningJob, false)) {
+ // Job became ready again while we were waiting to stop it (for example,
+ // the device was temporarily taken off the charger). Ignore the pending
+ // stop and see what the manager says.
+ mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
+ mPendingLegacyStopReason = 0;
+ mPendingDebugStopReason = null;
+ } else {
+ Slog.i(TAG, "JS was waiting to stop this job."
+ + " Sending onStop: " + getRunningJobNameLocked());
+ mParams.setStopReason(mPendingStopReason, mPendingLegacyStopReason,
+ mPendingDebugStopReason);
+ sendStopMessageLocked(mPendingDebugStopReason);
+ break;
+ }
+ }
final long latestStopTimeElapsed =
mExecutionStartTimeElapsed + mMaxExecutionTimeMillis;
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -886,6 +925,9 @@
mCancelled = false;
service = null;
mAvailable = true;
+ mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
+ mPendingLegacyStopReason = 0;
+ mPendingDebugStopReason = null;
removeOpTimeOutLocked();
mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
@@ -972,7 +1014,16 @@
pw.print(", ");
TimeUtils.formatDuration(
(mExecutionStartTimeElapsed + mMaxExecutionTimeMillis) - nowElapsed, pw);
- pw.println("]");
+ pw.print("]");
+ if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+ pw.print(" Pending stop because ");
+ pw.print(mPendingStopReason);
+ pw.print("/");
+ pw.print(mPendingLegacyStopReason);
+ pw.print("/");
+ pw.print(mPendingDebugStopReason);
+ }
+ pw.println();
pw.decreaseIndent();
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index aa8d98c..9cd3a8f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -541,18 +541,23 @@
/**
* Write out a tag with data identifying this job's constraints. If the constraint isn't here
* it doesn't apply.
+ * TODO: b/183455312 Update this code to use proper serialization for NetworkRequest,
+ * because currently store is not including everything (like, UIDs, bandwidth,
+ * signal strength etc. are lost).
*/
private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
if (jobStatus.hasConnectivityConstraint()) {
final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+ // STOPSHIP b/183071974: improve the scheme for backward compatibility and
+ // mainline cleanliness.
out.attribute(null, "net-capabilities", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+ BitUtils.packBits(network.getCapabilities())));
out.attribute(null, "net-unwanted-capabilities", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities())));
+ BitUtils.packBits(network.getUnwantedCapabilities())));
out.attribute(null, "net-transport-types", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
+ BitUtils.packBits(network.getTransportTypes())));
}
if (jobStatus.hasIdleConstraint()) {
out.attribute(null, "idle", Boolean.toString(true));
@@ -976,18 +981,23 @@
null, "net-unwanted-capabilities");
final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
if (netCapabilities != null && netTransportTypes != null) {
- final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder()
+ .clearCapabilities();
final long unwantedCapabilities = netUnwantedCapabilities != null
? Long.parseLong(netUnwantedCapabilities)
- : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities());
-
+ : BitUtils.packBits(builder.build().getUnwantedCapabilities());
// We're okay throwing NFE here; caught by caller
- request.networkCapabilities.setCapabilities(
- BitUtils.unpackBits(Long.parseLong(netCapabilities)),
- BitUtils.unpackBits(unwantedCapabilities));
- request.networkCapabilities.setTransportTypes(
- BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
- jobBuilder.setRequiredNetwork(request);
+ for (int capability : BitUtils.unpackBits(Long.parseLong(netCapabilities))) {
+ builder.addCapability(capability);
+ }
+ for (int unwantedCapability : BitUtils.unpackBits(
+ Long.parseLong(netUnwantedCapabilities))) {
+ builder.addUnwantedCapability(unwantedCapability);
+ }
+ for (int transport : BitUtils.unpackBits(Long.parseLong(netTransportTypes))) {
+ builder.addTransportType(transport);
+ }
+ jobBuilder.setRequiredNetwork(builder.build());
} else {
// Read legacy values
val = parser.getAttributeValue(null, "connectivity");
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 6e542f3..df21d75 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -22,6 +22,7 @@
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.net.ConnectivityManager;
@@ -380,15 +381,23 @@
}
}
+ private static NetworkCapabilities.Builder copyCapabilities(
+ @NonNull final NetworkRequest request) {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+ for (int transport : request.getTransportTypes()) builder.addTransportType(transport);
+ for (int capability : request.getCapabilities()) builder.addCapability(capability);
+ return builder;
+ }
+
private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
NetworkCapabilities capabilities, Constants constants) {
// A restricted job that's out of quota MUST use an unmetered network.
if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
&& !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
- final NetworkCapabilities required = new NetworkCapabilities.Builder(
- jobStatus.getJob().getRequiredNetwork().networkCapabilities)
- .addCapability(NET_CAPABILITY_NOT_METERED).build();
- return required.satisfiedByNetworkCapabilities(capabilities);
+ final NetworkCapabilities.Builder builder =
+ copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+ builder.addCapability(NET_CAPABILITY_NOT_METERED);
+ return builder.build().satisfiedByNetworkCapabilities(capabilities);
} else {
return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities);
}
@@ -402,10 +411,10 @@
}
// See if we match after relaxing any unmetered request
- final NetworkCapabilities relaxed = new NetworkCapabilities.Builder(
- jobStatus.getJob().getRequiredNetwork().networkCapabilities)
- .removeCapability(NET_CAPABILITY_NOT_METERED).build();
- if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
+ final NetworkCapabilities.Builder builder =
+ copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+ builder.removeCapability(NET_CAPABILITY_NOT_METERED);
+ if (builder.build().satisfiedByNetworkCapabilities(capabilities)) {
// TODO: treat this as "maybe" response; need to check quotas
return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
} else {
@@ -716,13 +725,6 @@
StateControllerProto.ConnectivityController.REQUESTED_STANDBY_EXCEPTION_UIDS,
mRequestedWhitelistJobs.keyAt(i));
}
- for (int i = 0; i < mAvailableNetworks.size(); i++) {
- Network network = mAvailableNetworks.keyAt(i);
- if (network != null) {
- network.dumpDebug(proto,
- StateControllerProto.ConnectivityController.AVAILABLE_NETWORKS);
- }
- }
for (int i = 0; i < mTrackedJobs.size(); i++) {
final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
for (int j = 0; j < jobs.size(); j++) {
@@ -736,12 +738,6 @@
StateControllerProto.ConnectivityController.TrackedJob.INFO);
proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
js.getSourceUid());
- NetworkRequest rn = js.getJob().getRequiredNetwork();
- if (rn != null) {
- rn.dumpDebug(proto,
- StateControllerProto.ConnectivityController.TrackedJob
- .REQUIRED_NETWORK);
- }
proto.end(jsToken);
}
}
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 659cfa7..8d999e1 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
@@ -30,6 +30,7 @@
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
import android.net.Network;
+import android.net.NetworkRequest;
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -38,6 +39,7 @@
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
+import android.util.Range;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -55,6 +57,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.function.Predicate;
/**
@@ -325,6 +328,12 @@
/** The evaluated priority of the job when it started running. */
public int lastEvaluatedPriority;
+ /**
+ * Whether or not this particular JobStatus instance was treated as an EJ when it started
+ * running. This isn't copied over when a job is rescheduled.
+ */
+ public boolean startedAsExpeditedJob = false;
+
// If non-null, this is work that has been enqueued for the job.
public ArrayList<JobWorkItem> pendingWork;
@@ -527,8 +536,15 @@
// Later, when we check if a given network satisfies the required
// network, we need to know the UID that is requesting it, so push
// our source UID into place.
- job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
+ final JobInfo.Builder builder = new JobInfo.Builder(job);
+ final NetworkRequest.Builder requestBuilder =
+ new NetworkRequest.Builder(job.getRequiredNetwork());
+ requestBuilder.setUids(
+ Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
+ builder.setRequiredNetwork(requestBuilder.build());
+ job = builder.build();
}
+
final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
mHasMediaBackupExemption = !job.hasLateConstraint() && exemptedMediaUrisOnly
&& requiresNetwork && this.sourcePackageName.equals(jsi.getMediaBackupPackage());
@@ -1112,18 +1128,19 @@
*/
public boolean canRunInDoze() {
return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
- || (shouldTreatAsExpeditedJob()
+ || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
&& (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
}
boolean canRunInBatterySaver() {
return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
- || (shouldTreatAsExpeditedJob()
+ || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
&& (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
}
boolean shouldIgnoreNetworkBlocking() {
- return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob();
+ return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
+ || (shouldTreatAsExpeditedJob() || startedAsExpeditedJob);
}
/** @return true if the constraint was changed, false otherwise. */
@@ -2022,7 +2039,10 @@
pw.println(serviceInfo != null);
if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) {
pw.print("readyWithinExpeditedQuota: ");
- pw.println(mReadyWithinExpeditedQuota);
+ pw.print(mReadyWithinExpeditedQuota);
+ pw.print(" (started as EJ: ");
+ pw.print(startedAsExpeditedJob);
+ pw.println(")");
}
pw.decreaseIndent();
@@ -2161,9 +2181,6 @@
if (uriPerms != null) {
uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS);
}
- if (job.getRequiredNetwork() != null) {
- job.getRequiredNetwork().dumpDebug(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK);
- }
if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_DOWNLOAD_BYTES,
mTotalNetworkDownloadBytes);
@@ -2252,10 +2269,6 @@
}
}
- if (network != null) {
- network.dumpDebug(proto, JobStatusDumpProto.NETWORK);
- }
-
if (pendingWork != null) {
for (int i = 0; i < pendingWork.size(); i++) {
dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i));
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 2b79969..91189e4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -849,10 +849,9 @@
return true;
}
// A job is within quota if one of the following is true:
- // 1. it's already running (already executing expedited jobs should be allowed to finish)
- // 2. the app is currently in the foreground
- // 3. the app overall is within its quota
- // 4. It's on the temp allowlist (or within the grace period)
+ // 1. the app is currently in the foreground
+ // 2. the app overall is within its quota
+ // 3. It's on the temp allowlist (or within the grace period)
if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) {
return true;
}
@@ -873,13 +872,6 @@
return true;
}
- Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
- // Any already executing expedited jobs should be allowed to finish.
- if (ejTimer != null && ejTimer.isRunning(jobStatus)) {
- return true;
- }
-
return 0 < getRemainingEJExecutionTimeLocked(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
}
@@ -4153,6 +4145,8 @@
pw.print(", ");
if (js.shouldTreatAsExpeditedJob()) {
pw.print("within EJ quota");
+ } else if (js.startedAsExpeditedJob) {
+ pw.print("out of EJ quota");
} else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
pw.print("within regular quota");
} else {
@@ -4163,6 +4157,8 @@
pw.print(getRemainingEJExecutionTimeLocked(
js.getSourceUserId(), js.getSourcePackageName()));
pw.print("ms remaining in EJ quota");
+ } else if (js.startedAsExpeditedJob) {
+ pw.print("should be stopped after min execution time");
} else {
pw.print(getRemainingExecutionTimeLocked(js));
pw.print("ms remaining in quota");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 24f7b37..a62e258 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -190,7 +190,7 @@
ONE_HOUR,
ONE_HOUR,
2 * ONE_HOUR,
- 4 * ONE_DAY
+ 4 * ONE_HOUR
};
private static final int[] THRESHOLD_BUCKETS = {
diff --git a/core/api/current.txt b/core/api/current.txt
index dd0ea28..af0a739 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -55,7 +55,9 @@
field public static final String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER";
field public static final String BLUETOOTH = "android.permission.BLUETOOTH";
field public static final String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN";
+ field public static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT";
field public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
+ field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED";
field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
@@ -194,6 +196,7 @@
field public static final String CONTACTS = "android.permission-group.CONTACTS";
field public static final String LOCATION = "android.permission-group.LOCATION";
field public static final String MICROPHONE = "android.permission-group.MICROPHONE";
+ field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
field public static final String PHONE = "android.permission-group.PHONE";
field public static final String SENSORS = "android.permission-group.SENSORS";
field public static final String SMS = "android.permission-group.SMS";
@@ -1176,6 +1179,7 @@
field public static final int reqNavigation = 16843306; // 0x101022a
field public static final int reqTouchScreen = 16843303; // 0x1010227
field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
+ field public static final int requestOptimizedExternalStorageAccess = 16844357; // 0x1010645
field public static final int requireDeviceScreenOn = 16844317; // 0x101061d
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
@@ -6316,13 +6320,13 @@
method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int);
method public static android.app.PendingIntent getActivity(android.content.Context, int, @NonNull android.content.Intent, int, @Nullable android.os.Bundle);
method public static android.app.PendingIntent getBroadcast(android.content.Context, int, @NonNull android.content.Intent, int);
- method @NonNull public String getCreatorPackage();
+ method @Nullable public String getCreatorPackage();
method public int getCreatorUid();
method @NonNull public android.os.UserHandle getCreatorUserHandle();
method public static android.app.PendingIntent getForegroundService(android.content.Context, int, @NonNull android.content.Intent, int);
method @NonNull public android.content.IntentSender getIntentSender();
method public static android.app.PendingIntent getService(android.content.Context, int, @NonNull android.content.Intent, int);
- method @Deprecated @NonNull public String getTargetPackage();
+ method @Deprecated @Nullable public String getTargetPackage();
method public boolean isActivity();
method public boolean isBroadcast();
method public boolean isForegroundService();
@@ -7183,6 +7187,7 @@
method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName);
method public boolean isDeviceIdAttestationSupported();
method public boolean isDeviceOwnerApp(String);
+ method public boolean isEnterpriseNetworkPreferenceEnabled();
method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
method public boolean isKeyPairGrantedToWifiAuth(@NonNull String);
method public boolean isLockTaskPermitted(String);
@@ -7190,7 +7195,6 @@
method public boolean isManagedProfile(@NonNull android.content.ComponentName);
method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
- method public boolean isNetworkSlicingEnabled();
method public boolean isOrganizationOwnedDeviceWithManagedProfile();
method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -7245,6 +7249,7 @@
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+ method public void setEnterpriseNetworkPreferenceEnabled(boolean);
method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
@@ -7264,7 +7269,6 @@
method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
- method public void setNetworkSlicingEnabled(boolean);
method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int);
method public void setOrganizationId(@NonNull String);
method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -8996,6 +9000,7 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getName();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getType();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.os.ParcelUuid[] getUuids();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPairingConfirmation(boolean);
method public boolean setPin(byte[]);
method public void writeToParcel(android.os.Parcel, int);
@@ -21341,6 +21346,7 @@
method public void setCallback(@Nullable android.media.MediaCodec.Callback, @Nullable android.os.Handler);
method public void setCallback(@Nullable android.media.MediaCodec.Callback);
method public void setInputSurface(@NonNull android.view.Surface);
+ method public void setOnFirstTunnelFrameReadyListener(@Nullable android.os.Handler, @Nullable android.media.MediaCodec.OnFirstTunnelFrameReadyListener);
method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
method public void setOutputSurface(@NonNull android.view.Surface);
method public void setParameters(@Nullable android.os.Bundle);
@@ -21368,6 +21374,7 @@
field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us";
+ field public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek";
field public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
@@ -21458,6 +21465,10 @@
field public static final String WIDTH = "android.media.mediacodec.width";
}
+ public static interface MediaCodec.OnFirstTunnelFrameReadyListener {
+ method public void onFirstTunnelFrameReady(@NonNull android.media.MediaCodec);
+ }
+
public static interface MediaCodec.OnFrameRenderedListener {
method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long);
}
@@ -26956,7 +26967,7 @@
public abstract static class VcnManager.VcnStatusCallback {
ctor public VcnManager.VcnStatusCallback();
method public abstract void onGatewayConnectionError(@NonNull int[], int, @Nullable Throwable);
- method public abstract void onVcnStatusChanged(int);
+ method public abstract void onStatusChanged(int);
}
}
@@ -34908,7 +34919,7 @@
field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS";
field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
- field public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS = "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS";
+ field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
@@ -35539,6 +35550,9 @@
method public static android.net.Uri getUriForSubscriptionIdAndField(int, String);
field public static final String AUTHORITY = "service-state";
field public static final android.net.Uri CONTENT_URI;
+ field public static final String DATA_NETWORK_TYPE = "data_network_type";
+ field public static final String DATA_REG_STATE = "data_reg_state";
+ field public static final String DUPLEX_MODE = "duplex_mode";
field public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
field public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
field public static final String VOICE_REG_STATE = "voice_reg_state";
@@ -40538,6 +40552,7 @@
field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool";
field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool";
field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
+ field public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL = "display_call_strength_indicator_bool";
field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
field public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
field public static final String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -40562,6 +40577,7 @@
field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
+ field public static final String KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL = "hide_no_calling_indicator_on_data_network_bool";
field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool";
field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -40727,6 +40743,7 @@
field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int";
field public static final String KEY_PREFIX = "ims.";
field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool";
+ field public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = "ims.rcs_feature_tag_allowed_string_array";
field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int";
}
@@ -42043,7 +42060,6 @@
method public static int[] calculateLength(String, boolean);
method @Deprecated public static android.telephony.SmsMessage createFromPdu(byte[]);
method public static android.telephony.SmsMessage createFromPdu(byte[], String);
- method @Nullable public static android.telephony.SmsMessage createSmsSubmitPdu(@NonNull byte[], boolean);
method public String getDisplayMessageBody();
method public String getDisplayOriginatingAddress();
method public String getEmailBody();
@@ -42144,6 +42160,7 @@
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
+ method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int);
method public int getDeviceToDeviceStatusSharingPreference(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
method public static int getSlotIndex(int);
@@ -42157,6 +42174,7 @@
method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(@NonNull java.util.List<android.net.Uri>, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
method public void setSubscriptionOverrideCongested(int, boolean, long);
@@ -42172,8 +42190,9 @@
field public static final int D2D_SHARING_ALL = 3; // 0x3
field public static final int D2D_SHARING_ALL_CONTACTS = 1; // 0x1
field public static final int D2D_SHARING_DISABLED = 0; // 0x0
- field public static final int D2D_SHARING_STARRED_CONTACTS = 2; // 0x2
+ field public static final int D2D_SHARING_SELECTED_CONTACTS = 2; // 0x2
field public static final String D2D_STATUS_SHARING = "d2d_sharing_status";
+ field public static final String D2D_STATUS_SHARING_SELECTED_CONTACTS = "d2d_sharing_contacts";
field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
field public static final int DEFAULT_SUBSCRIPTION_ID = 2147483647; // 0x7fffffff
@@ -42329,7 +42348,7 @@
field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
- field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5; // 0x5
field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
}
@@ -42491,6 +42510,7 @@
field public static final int CALL_STATE_IDLE = 0; // 0x0
field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
field public static final int CALL_STATE_RINGING = 1; // 0x1
+ field public static final String CAPABILITY_SLICING_CONFIG_SUPPORTED = "CAPABILITY_SLICING_CONFIG_SUPPORTED";
field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
@@ -48080,6 +48100,7 @@
}
public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
+ ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method public int describeContents();
method public void release();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -52559,6 +52580,7 @@
method public void addTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationContext);
method @NonNull @WorkerThread public java.util.Set<android.view.translation.TranslationCapability> getTranslationCapabilities(int, int);
+ method @Nullable public android.app.PendingIntent getTranslationSettingsActivityIntent();
method public void removeTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e3b7c88..8dc5c15 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -312,6 +312,10 @@
field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 0; // 0x0
}
+ public final class StorageVolume implements android.os.Parcelable {
+ method @NonNull public android.os.UserHandle getOwner();
+ }
+
}
package android.provider {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a2f895b..008b649 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -267,6 +267,7 @@
field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
+ field public static final String SUGGEST_EXTERNAL_TIME = "android.permission.SUGGEST_EXTERNAL_TIME";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY";
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
@@ -699,6 +700,9 @@
method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int);
method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean);
+ field public static final int PASSWORD = 0; // 0x0
+ field public static final int PATTERN = 2; // 0x2
+ field public static final int PIN = 1; // 0x1
}
public class Notification implements android.os.Parcelable {
@@ -2174,6 +2178,7 @@
package android.companion {
public final class CompanionDeviceManager {
+ method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, int);
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
}
@@ -2440,6 +2445,7 @@
package android.content.pm {
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+ method @Nullable public Boolean hasRequestOptimizedExternalStorageAccess();
method public boolean isEncryptionAware();
method public boolean isInstantApp();
method public boolean isOem();
@@ -2869,14 +2875,11 @@
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames();
method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
- method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @CheckResult @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @CheckResult @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
field public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; // 0x1
- field public static final int ERROR_DOMAIN_SET_ID_NULL = 2; // 0x2
- field public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; // 0x3
- field public static final int ERROR_INVALID_STATE_CODE = 6; // 0x6
- field public static final int ERROR_UNABLE_TO_APPROVE = 5; // 0x5
- field public static final int ERROR_UNKNOWN_DOMAIN = 4; // 0x4
+ field public static final int ERROR_UNABLE_TO_APPROVE = 3; // 0x3
+ field public static final int ERROR_UNKNOWN_DOMAIN = 2; // 0x2
field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
field public static final int STATUS_OK = 0; // 0x0
}
@@ -5228,6 +5231,7 @@
method @Nullable public String getClientPackageName();
method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
+ method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback);
method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
method public void startScan();
method public void stopScan();
@@ -5429,6 +5433,8 @@
public final class MediaSessionManager {
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.media.session.MediaSession.Token getMediaKeyEventSession();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public String getMediaKeyEventSessionPackageName();
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
@@ -7777,6 +7783,7 @@
method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback);
+ method public boolean registerCountryCodeChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback);
method public void setOnServiceDeadCallback(@NonNull Runnable);
method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback);
@@ -7789,6 +7796,7 @@
method public boolean tearDownClientInterface(@NonNull String);
method public boolean tearDownInterfaces();
method public boolean tearDownSoftApInterface(@NonNull String);
+ method public void unregisterCountryCodeChangeListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR";
field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1
field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0
@@ -7799,6 +7807,10 @@
field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
}
+ public static interface WifiNl80211Manager.CountryCodeChangeListener {
+ method public void onChanged(@NonNull String);
+ }
+
public static class WifiNl80211Manager.OemSecurityType {
ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
field public final int groupCipher;
@@ -8497,6 +8509,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser();
@@ -8510,6 +8523,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean sharesMediaWithParent();
field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
@@ -8523,6 +8537,7 @@
field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2
field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
+ field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
}
@@ -8919,6 +8934,7 @@
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
+ field public static final String NAMESPACE_APPSEARCH = "appsearch";
field public static final String NAMESPACE_APP_COMPAT = "app_compat";
field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
@@ -10324,7 +10340,6 @@
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
- field public static final int HOTWORD_DETECTION_FALSE_ALERT = 0; // 0x0
field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
@@ -10347,7 +10362,7 @@
method public abstract void onError();
method public abstract void onRecognitionPaused();
method public abstract void onRecognitionResumed();
- method public void onRejected(int);
+ method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
}
public static class AlwaysOnHotwordDetector.EventPayload {
@@ -10390,13 +10405,13 @@
ctor public HotwordDetectionService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onDetectFromDspSource(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, long, @NonNull android.service.voice.HotwordDetectionService.DspHotwordDetectionCallback);
- method public void onUpdateState(@Nullable android.os.Bundle, @Nullable android.os.SharedMemory);
+ method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
field public static final String SERVICE_INTERFACE = "android.service.voice.HotwordDetectionService";
}
public static final class HotwordDetectionService.DspHotwordDetectionCallback {
method public void onDetected();
- method public void onRejected();
+ method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
}
public interface HotwordDetector {
@@ -10415,7 +10430,7 @@
public class VoiceInteractionService extends android.app.Service {
method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
- method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.Bundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
}
@@ -11255,12 +11270,11 @@
public final class PhoneCapability implements android.os.Parcelable {
method public int describeContents();
- method public int getDeviceNrCapabilityBitmask();
- method @IntRange(from=1) public int getMaxActiveInternetData();
- method @IntRange(from=1) public int getMaxActivePacketSwitchedVoiceCalls();
+ method @NonNull public int[] getDeviceNrCapabilities();
+ method @IntRange(from=1) public int getMaxActiveDataSubscriptions();
+ method @IntRange(from=1) public int getMaxActiveVoiceSubscriptions();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhoneCapability> CREATOR;
- field public static final int DEVICE_NR_CAPABILITY_NONE = 0; // 0x0
field public static final int DEVICE_NR_CAPABILITY_NSA = 1; // 0x1
field public static final int DEVICE_NR_CAPABILITY_SA = 2; // 0x2
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ae1cbf7..4d0c765 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -34,6 +34,7 @@
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
+ field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
@@ -817,6 +818,7 @@
method public int describeContents();
method public android.os.UserHandle getUserHandle();
method public boolean isAdmin();
+ method public boolean isCloneProfile();
method public boolean isDemo();
method public boolean isEnabled();
method public boolean isEphemeral();
@@ -1702,6 +1704,7 @@
method public static android.os.VibrationEffect get(int, boolean);
method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
method public abstract long getDuration();
+ method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform();
field public static final int EFFECT_POP = 4; // 0x4
field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
@@ -1711,35 +1714,30 @@
field public static final int[] RINGTONES;
}
- public static class VibrationEffect.OneShot extends android.os.VibrationEffect implements android.os.Parcelable {
- ctor public VibrationEffect.OneShot(android.os.Parcel);
- ctor public VibrationEffect.OneShot(long, int);
- method public int getAmplitude();
- method public long getDuration();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.OneShot> CREATOR;
- }
-
- public static class VibrationEffect.Prebaked extends android.os.VibrationEffect implements android.os.Parcelable {
- ctor public VibrationEffect.Prebaked(android.os.Parcel);
- ctor public VibrationEffect.Prebaked(int, boolean, int);
- method public long getDuration();
- method public int getEffectStrength();
- method public int getId();
- method public boolean shouldFallback();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Prebaked> CREATOR;
- }
-
- public static class VibrationEffect.Waveform extends android.os.VibrationEffect implements android.os.Parcelable {
- ctor public VibrationEffect.Waveform(android.os.Parcel);
- ctor public VibrationEffect.Waveform(long[], int[], int);
- method public int[] getAmplitudes();
+ public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+ method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int);
method public long getDuration();
method public int getRepeatIndex();
- method public long[] getTimings();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Waveform> CREATOR;
+ method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
+ method @NonNull public android.os.VibrationEffect.Composed resolve(int);
+ method @NonNull public android.os.VibrationEffect.Composed scale(float);
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
+ }
+
+ public static final class VibrationEffect.Composition {
+ method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect);
+ method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect, @IntRange(from=0) int);
+ }
+
+ public static final class VibrationEffect.WaveformBuilder {
+ method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
+ method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int);
+ method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
+ method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int);
+ method @NonNull public android.os.VibrationEffect build();
+ method @NonNull public android.os.VibrationEffect build(int);
}
public class VintfObject {
@@ -1840,6 +1838,7 @@
public class StorageManager {
method @NonNull public static java.util.UUID convert(@NonNull String);
method @NonNull public static String convert(@NonNull java.util.UUID);
+ method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
method public static boolean isUserKeyUnlocked(int);
}
@@ -1856,6 +1855,80 @@
}
+package android.os.vibrator {
+
+ public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment {
+ method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int);
+ method public int describeContents();
+ method public long getDuration();
+ method public int getEffectId();
+ method public int getEffectStrength();
+ method public boolean hasNonZeroAmplitude();
+ method @NonNull public android.os.vibrator.PrebakedSegment resolve(int);
+ method @NonNull public android.os.vibrator.PrebakedSegment scale(float);
+ method public boolean shouldFallback();
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR;
+ }
+
+ public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment {
+ method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int);
+ method public int describeContents();
+ method public int getDelay();
+ method public long getDuration();
+ method public int getPrimitiveId();
+ method public float getScale();
+ method public boolean hasNonZeroAmplitude();
+ method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int);
+ method @NonNull public android.os.vibrator.PrimitiveSegment scale(float);
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR;
+ }
+
+ public final class RampSegment extends android.os.vibrator.VibrationEffectSegment {
+ method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int);
+ method public int describeContents();
+ method public long getDuration();
+ method public float getEndAmplitude();
+ method public float getEndFrequency();
+ method public float getStartAmplitude();
+ method public float getStartFrequency();
+ method public boolean hasNonZeroAmplitude();
+ method @NonNull public android.os.vibrator.RampSegment resolve(int);
+ method @NonNull public android.os.vibrator.RampSegment scale(float);
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR;
+ }
+
+ public final class StepSegment extends android.os.vibrator.VibrationEffectSegment {
+ method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int);
+ method public int describeContents();
+ method public float getAmplitude();
+ method public long getDuration();
+ method public float getFrequency();
+ method public boolean hasNonZeroAmplitude();
+ method @NonNull public android.os.vibrator.StepSegment resolve(int);
+ method @NonNull public android.os.vibrator.StepSegment scale(float);
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR;
+ }
+
+ public abstract class VibrationEffectSegment implements android.os.Parcelable {
+ method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int);
+ method public abstract long getDuration();
+ method public abstract boolean hasNonZeroAmplitude();
+ method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int);
+ method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float);
+ method public abstract void validate();
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR;
+ }
+
+}
+
package android.permission {
public final class PermissionControllerManager {
@@ -2260,6 +2333,7 @@
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
method public int getDataNetworkType();
+ method public int getDataRegState();
method public void setCdmaSystemAndNetworkId(int, int);
method public void setCellBandwidths(int[]);
method public void setChannelNumber(int);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index a536efb..1833ed5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1515,30 +1515,6 @@
MissingNullability: android.os.VibrationEffect#get(int, boolean):
-MissingNullability: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.OneShot#scale(float, int):
-
-MissingNullability: android.os.VibrationEffect.OneShot#writeToParcel(android.os.Parcel, int) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Prebaked#writeToParcel(android.os.Parcel, int) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #1:
-
-MissingNullability: android.os.VibrationEffect.Waveform#getAmplitudes():
-
-MissingNullability: android.os.VibrationEffect.Waveform#getTimings():
-
-MissingNullability: android.os.VibrationEffect.Waveform#scale(float, int):
-
-MissingNullability: android.os.VibrationEffect.Waveform#writeToParcel(android.os.Parcel, int) parameter #0:
-
MissingNullability: android.os.VintfObject#getHalNamesAndVersions():
MissingNullability: android.os.VintfObject#getSepolicyVersion():
@@ -2739,12 +2715,6 @@
ParcelConstructor: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel):
-ParcelConstructor: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel):
-
-ParcelConstructor: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel):
-
-ParcelConstructor: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel):
-
ParcelConstructor: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.Parcel):
ParcelConstructor: android.service.notification.SnoozeCriterion#SnoozeCriterion(android.os.Parcel):
@@ -2773,12 +2743,6 @@
ParcelCreator: android.net.metrics.ValidationProbeEvent:
-ParcelCreator: android.os.VibrationEffect.OneShot:
-
-ParcelCreator: android.os.VibrationEffect.Prebaked:
-
-ParcelCreator: android.os.VibrationEffect.Waveform:
-
ParcelCreator: android.service.autofill.InternalOnClickAction:
ParcelCreator: android.service.autofill.InternalSanitizer:
@@ -2797,12 +2761,6 @@
ParcelNotFinal: android.os.IncidentManager.IncidentReport:
-ParcelNotFinal: android.os.VibrationEffect.OneShot:
-
-ParcelNotFinal: android.os.VibrationEffect.Prebaked:
-
-ParcelNotFinal: android.os.VibrationEffect.Waveform:
-
ParcelNotFinal: android.os.health.HealthStatsParceler:
ParcelNotFinal: android.service.autofill.InternalOnClickAction:
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 27b19bc..a6aa28e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1073,6 +1073,8 @@
/** @hide */
@UnsupportedAppUsage
public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN;
+ /** @hide */
+ public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT;
/** @hide Use the BiometricPrompt/BiometricManager APIs. */
public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC;
/** @hide Physical activity recognition. */
@@ -1221,7 +1223,7 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 111;
+ public static final int _NUM_OP = 112;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1465,6 +1467,8 @@
public static final String OPSTR_START_FOREGROUND = "android:start_foreground";
/** @hide */
public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan";
+ /** @hide */
+ public static final String OPSTR_BLUETOOTH_CONNECT = "android:bluetooth_connect";
/** @hide Use the BiometricPrompt/BiometricManager APIs. */
public static final String OPSTR_USE_BIOMETRIC = "android:use_biometric";
@@ -1696,6 +1700,9 @@
OP_WRITE_MEDIA_VIDEO,
OP_READ_MEDIA_IMAGES,
OP_WRITE_MEDIA_IMAGES,
+ // Nearby devices
+ OP_BLUETOOTH_SCAN,
+ OP_BLUETOOTH_CONNECT,
// APPOP PERMISSIONS
OP_ACCESS_NOTIFICATIONS,
@@ -1801,7 +1808,7 @@
OP_ACCEPT_HANDOVER, // ACCEPT_HANDOVER
OP_MANAGE_IPSEC_TUNNELS, // MANAGE_IPSEC_HANDOVERS
OP_START_FOREGROUND, // START_FOREGROUND
- OP_COARSE_LOCATION, // BLUETOOTH_SCAN
+ OP_BLUETOOTH_SCAN, // BLUETOOTH_SCAN
OP_USE_BIOMETRIC, // BIOMETRIC
OP_ACTIVITY_RECOGNITION, // ACTIVITY_RECOGNITION
OP_SMS_FINANCIAL_TRANSACTIONS, // SMS_FINANCIAL_TRANSACTIONS
@@ -1835,6 +1842,7 @@
OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE
OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE
OP_MANAGE_MEDIA, // MANAGE_MEDIA
+ OP_BLUETOOTH_CONNECT, // OP_BLUETOOTH_CONNECT
};
/**
@@ -1952,6 +1960,7 @@
OPSTR_FINE_LOCATION_SOURCE,
OPSTR_COARSE_LOCATION_SOURCE,
OPSTR_MANAGE_MEDIA,
+ OPSTR_BLUETOOTH_CONNECT,
};
/**
@@ -2070,6 +2079,7 @@
"FINE_LOCATION_SOURCE",
"COARSE_LOCATION_SOURCE",
"MANAGE_MEDIA",
+ "BLUETOOTH_CONNECT",
};
/**
@@ -2155,7 +2165,7 @@
Manifest.permission.ACCEPT_HANDOVER,
Manifest.permission.MANAGE_IPSEC_TUNNELS,
Manifest.permission.FOREGROUND_SERVICE,
- null, // no permission for OP_BLUETOOTH_SCAN
+ Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.USE_BIOMETRIC,
Manifest.permission.ACTIVITY_RECOGNITION,
Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
@@ -2189,6 +2199,7 @@
null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE,
null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE,
Manifest.permission.MANAGE_MEDIA,
+ Manifest.permission.BLUETOOTH_CONNECT,
};
/**
@@ -2308,6 +2319,7 @@
null, // ACCESS_FINE_LOCATION_SOURCE
null, // ACCESS_COARSE_LOCATION_SOURCE
null, // MANAGE_MEDIA
+ null, // BLUETOOTH_CONNECT
};
/**
@@ -2426,6 +2438,7 @@
null, // ACCESS_FINE_LOCATION_SOURCE
null, // ACCESS_COARSE_LOCATION_SOURCE
null, // MANAGE_MEDIA
+ null, // BLUETOOTH_CONNECT
};
/**
@@ -2543,6 +2556,7 @@
AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE
AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE
AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA
+ AppOpsManager.MODE_ALLOWED, // BLUETOOTH_CONNECT
};
/**
@@ -2664,6 +2678,7 @@
false, // ACCESS_FINE_LOCATION_SOURCE
false, // ACCESS_COARSE_LOCATION_SOURCE
false, // MANAGE_MEDIA
+ false, // BLUETOOTH_CONNECT
};
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0358fe5..8cbf76e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1995,8 +1995,8 @@
final String errorMessage = "Tried to access visual service "
+ SystemServiceRegistry.getSystemServiceClassName(name)
+ " from a non-visual Context:" + getOuterContext();
- final String message = "Visual services, such as WindowManager"
- + "or LayoutInflater should be accessed from Activity or other visual "
+ final String message = "Visual services, such as WindowManager "
+ + "or LayoutInflater should be accessed from Activity or another visual "
+ "Context. Use an Activity or a Context created with "
+ "Context#createWindowContext(int, Bundle), which are adjusted to "
+ "the configuration and visual bounds of an area on screen.";
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index b6d25cf..4326c2d 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -133,6 +133,42 @@
*/
public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm";
+ /**
+ *
+ * Password lock type, see {@link #setLock}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PASSWORD = 0;
+
+ /**
+ *
+ * Pin lock type, see {@link #setLock}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PIN = 1;
+
+ /**
+ *
+ * Pattern lock type, see {@link #setLock}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PATTERN = 2;
+
+ /**
+ * Available lock types
+ */
+ @IntDef({
+ PASSWORD,
+ PIN,
+ PATTERN
+ })
+ @interface LockTypes {}
/**
* Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
@@ -695,7 +731,7 @@
PasswordMetrics adminMetrics =
devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId());
// Check if the password fits the mold of a pin or pattern.
- boolean isPinOrPattern = lockType != LockTypes.PASSWORD;
+ boolean isPinOrPattern = lockType != PASSWORD;
return PasswordMetrics.validatePassword(
adminMetrics, complexity, isPinOrPattern, password).size() == 0;
@@ -759,7 +795,7 @@
boolean success = false;
try {
switch (lockType) {
- case LockTypes.PASSWORD:
+ case PASSWORD:
CharSequence passwordStr = new String(password, Charset.forName("UTF-8"));
lockPatternUtils.setLockCredential(
LockscreenCredential.createPassword(passwordStr),
@@ -767,7 +803,7 @@
userId);
success = true;
break;
- case LockTypes.PIN:
+ case PIN:
CharSequence pinStr = new String(password);
lockPatternUtils.setLockCredential(
LockscreenCredential.createPin(pinStr),
@@ -775,7 +811,7 @@
userId);
success = true;
break;
- case LockTypes.PATTERN:
+ case PATTERN:
List<LockPatternView.Cell> pattern =
LockPatternUtils.byteArrayToPattern(password);
lockPatternUtils.setLockCredential(
@@ -796,18 +832,4 @@
}
return success;
}
-
- /**
- * Available lock types
- */
- @IntDef({
- LockTypes.PASSWORD,
- LockTypes.PIN,
- LockTypes.PATTERN
- })
- @interface LockTypes {
- int PASSWORD = 0;
- int PIN = 1;
- int PATTERN = 2;
- }
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4eda6fe..420ec08 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -39,7 +39,6 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
@@ -5217,8 +5216,7 @@
R.dimen.notification_right_icon_size) / density;
float viewWidthDp = viewHeightDp; // icons are 1:1 by default
if (largeIconShown && (p.mPromotePicture
- || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
- || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) {
+ || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
Drawable drawable = mN.mLargeIcon.loadDrawable(mContext);
if (drawable != null) {
int iconWidth = drawable.getIntrinsicWidth();
@@ -5285,8 +5283,7 @@
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors except when low-priority mode prevents that
if (!p.mReduceHighlights) {
- textColor = getBackgroundColor(p);
- pillColor = getAccentColor(p);
+ pillColor = getAccentTertiaryColor(p);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -5662,55 +5659,45 @@
if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
return false;
}
- final ContentResolver contentResolver = mContext.getContentResolver();
- final int decorationType = DevFlags.getFullyCustomViewNotifDecoration(contentResolver);
- return decorationType != DevFlags.DECORATION_NONE
- && (DevFlags.shouldBackportSNotifRules(contentResolver)
- || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S);
+ return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
}
private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
- int decorationType =
- DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
- .decorationType(decorationType)
+ .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
.fillTextsFrom(this);
TemplateBindResult result = new TemplateBindResult();
RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mContext, standard, customContent,
- p, result, decorationType);
+ p, result);
return standard;
}
private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
- int decorationType =
- DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG)
- .decorationType(decorationType)
+ .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
.fillTextsFrom(this);
TemplateBindResult result = new TemplateBindResult();
RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
p, result);
buildCustomContentIntoTemplate(mContext, standard, customContent,
- p, result, decorationType);
+ p, result);
return standard;
}
private RemoteViews minimallyDecoratedHeadsUpContentView(
@NonNull RemoteViews customContent) {
- int decorationType =
- DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
- .decorationType(decorationType)
+ .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
.fillTextsFrom(this);
TemplateBindResult result = new TemplateBindResult();
RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
p, result);
buildCustomContentIntoTemplate(mContext, standard, customContent,
- p, result, decorationType);
+ p, result);
return standard;
}
@@ -5769,12 +5756,6 @@
.fillTextsFrom(this);
result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
null /* result */);
- } else if (DevFlags.shouldBackportSNotifRules(mContext.getContentResolver())
- && useExistingRemoteView()
- && fullyCustomViewRequiresDecoration(false /* fromStyle */)) {
- // This "backport" logic is a special case to handle the UNDO style of notif
- // so that we can see what that will look like when the app targets S.
- result = minimallyDecoratedBigContentView(mN.contentView);
}
}
makeHeaderExpanded(result);
@@ -6237,6 +6218,25 @@
}
/**
+ * Gets the tertiary accent color for colored UI elements. If we're tinting with the theme
+ * accent, this comes from the tertiary system accent palette, otherwise this would be
+ * identical to {@link #getSmallIconColor(StandardTemplateParams)}.
+ */
+ private @ColorInt int getAccentTertiaryColor(StandardTemplateParams p) {
+ if (isColorized(p)) {
+ return getPrimaryTextColor(p);
+ }
+ if (mTintWithThemeAccent) {
+ int color = obtainThemeColor(com.android.internal.R.attr.colorAccentTertiary,
+ COLOR_INVALID);
+ if (color != COLOR_INVALID) {
+ return color;
+ }
+ }
+ return getContrastColor(p);
+ }
+
+ /**
* Gets the theme's error color, or the primary text color for colorized notifications.
*/
private @ColorInt int getErrorColor(StandardTemplateParams p) {
@@ -6864,24 +6864,18 @@
private static void buildCustomContentIntoTemplate(@NonNull Context context,
@NonNull RemoteViews template, @Nullable RemoteViews customContent,
- @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result,
- int decorationType) {
+ @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) {
int childIndex = -1;
if (customContent != null) {
// Need to clone customContent before adding, because otherwise it can no longer be
// parceled independently of remoteViews.
customContent = customContent.clone();
if (p.mHeaderless) {
- if (decorationType <= DevFlags.DECORATION_PARTIAL) {
- template.removeFromParent(R.id.notification_top_line);
- }
- // The vertical margins are bigger in the "two-line" scenario than the "one-line"
- // scenario, but the 'compatible' decoration state is intended to have 3 lines,
- // (1 for the top line views and 2 for the custom views), so in that one case we
- // use the smaller 1-line margins. This gives the compatible case 88-16*2=56 dp of
- // height, 24dp of which goes to the top line, leaving 32dp for the custom view.
- boolean hasSecondLine = decorationType != DevFlags.DECORATION_FULL_COMPATIBLE;
- Builder.setHeaderlessVerticalMargins(template, p, hasSecondLine);
+ template.removeFromParent(R.id.notification_top_line);
+ // We do not know how many lines ar emote view has, so we presume it has 2; this
+ // ensures that we don't under-pad the content, which could lead to abuse, at the
+ // cost of making single-line custom content over-padded.
+ Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */);
} else {
// also update the end margin to account for the large icon or expander
Resources resources = context.getResources();
@@ -9042,8 +9036,7 @@
template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
// Add custom view if provided by subclass.
- buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
- DevFlags.DECORATION_PARTIAL);
+ buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
return template;
}
@@ -9066,8 +9059,7 @@
template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
- buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
- DevFlags.DECORATION_PARTIAL);
+ buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
return template;
}
}
@@ -9644,16 +9636,15 @@
if (mBuilder.mActions.size() == 0) {
return makeStandardTemplateWithCustomContent(headsUpContentView);
}
- int decorationType = getDecorationType();
TemplateBindResult result = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
- .decorationType(decorationType)
+ .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
mBuilder.getHeadsUpBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
- p, result, decorationType);
+ p, result);
return remoteViews;
}
@@ -9661,16 +9652,15 @@
if (customContent == null) {
return null; // no custom view; use the default behavior
}
- int decorationType = getDecorationType();
TemplateBindResult result = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
- .decorationType(decorationType)
+ .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplate(
mBuilder.getBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
- p, result, decorationType);
+ p, result);
return remoteViews;
}
@@ -9681,24 +9671,18 @@
if (bigContentView == null) {
return null; // no custom view; use the default behavior
}
- int decorationType = getDecorationType();
TemplateBindResult result = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG)
- .decorationType(decorationType)
+ .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
mBuilder.getBigBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
- p, result, decorationType);
+ p, result);
return remoteViews;
}
- private int getDecorationType() {
- ContentResolver contentResolver = mBuilder.mContext.getContentResolver();
- return DevFlags.getDecoratedCustomViewNotifDecoration(contentResolver);
- }
-
/**
* @hide
*/
@@ -12091,6 +12075,22 @@
}
private static class StandardTemplateParams {
+ /**
+ * Notifications will be minimally decorated with ONLY an icon and expander:
+ * <li>A large icon is never shown.
+ * <li>A progress bar is never shown.
+ * <li>The expanded and heads up states do not show actions, even if provided.
+ */
+ public static final int DECORATION_MINIMAL = 1;
+
+ /**
+ * Notifications will be partially decorated with AT LEAST an icon and expander:
+ * <li>A large icon is shown if provided.
+ * <li>A progress bar is shown if provided and enough space remains below the content.
+ * <li>Actions are shown in the expanded and heads up states.
+ */
+ public static final int DECORATION_PARTIAL = 2;
+
public static int VIEW_TYPE_UNSPECIFIED = 0;
public static int VIEW_TYPE_NORMAL = 1;
public static int VIEW_TYPE_BIG = 2;
@@ -12276,112 +12276,20 @@
}
public StandardTemplateParams decorationType(int decorationType) {
- boolean hideTitle = decorationType <= DevFlags.DECORATION_FULL_COMPATIBLE;
- boolean hideOtherFields = decorationType <= DevFlags.DECORATION_MINIMAL;
- hideTitle(hideTitle);
+ // These fields are removed by the decoration process, and thus would not show anyway;
+ // hiding them is a minimal time/space optimization.
+ hideAppName(true);
+ hideTitle(true);
+ hideSubText(true);
+ hideTime(true);
+ // Minimally decorated custom views do not show certain pieces of chrome that have
+ // always been shown when using DecoratedCustomViewStyle.
+ boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
hideLargeIcon(hideOtherFields);
hideProgress(hideOtherFields);
hideActions(hideOtherFields);
+ hideSnoozeButton(hideOtherFields);
return this;
}
}
-
- /**
- * A class to centrally access various developer flags related to notifications.
- * This class is a non-final wrapper around Settings.Global which allows mocking for unit tests.
- * TODO(b/176239013): Try to remove this before shipping S
- * @hide
- */
- public static class DevFlags {
-
- /**
- * Notifications will not be decorated. The custom content will be shown as-is.
- *
- * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle,
- * as that API contract includes decorations that this does not provide.
- */
- public static final int DECORATION_NONE = 0;
-
- /**
- * Notifications will be minimally decorated with ONLY an icon and expander as follows:
- * <li>A large icon is never shown.
- * <li>A progress bar is never shown.
- * <li>The expanded and heads up states do not show actions, even if provided.
- * <li>The collapsed state gives the app's custom content 48dp of vertical space.
- * <li>The collapsed state does NOT include the top line of views,
- * like the alerted icon or work profile badge.
- *
- * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle,
- * as that API contract includes decorations that this does not provide.
- */
- public static final int DECORATION_MINIMAL = 1;
-
- /**
- * Notifications will be partially decorated with AT LEAST an icon and expander as follows:
- * <li>A large icon is shown if provided.
- * <li>A progress bar is shown if provided and enough space remains below the content.
- * <li>Actions are shown in the expanded and heads up states.
- * <li>The collapsed state gives the app's custom content 48dp of vertical space.
- * <li>The collapsed state does NOT include the top line of views,
- * like the alerted icon or work profile badge.
- */
- public static final int DECORATION_PARTIAL = 2;
-
- /**
- * Notifications will be fully decorated as follows:
- * <li>A large icon is shown if provided.
- * <li>A progress bar is shown if provided and enough space remains below the content.
- * <li>Actions are shown in the expanded and heads up states.
- * <li>The collapsed state gives the app's custom content 40dp of vertical space.
- * <li>The collapsed state DOES include the top line of views,
- * like the alerted icon or work profile badge.
- * <li>The collapsed state's top line views will never show the title text.
- */
- public static final int DECORATION_FULL_COMPATIBLE = 3;
-
- /**
- * Notifications will be fully decorated as follows:
- * <li>A large icon is shown if provided.
- * <li>A progress bar is shown if provided and enough space remains below the content.
- * <li>Actions are shown in the expanded and heads up states.
- * <li>The collapsed state gives the app's custom content 20dp of vertical space.
- * <li>The collapsed state DOES include the top line of views
- * like the alerted icon or work profile badge.
- * <li>The collapsed state's top line views will contain the title text if provided.
- */
- public static final int DECORATION_FULL_CONSTRAINED = 4;
-
- /**
- * Used by unit tests to force that this class returns its default values, which is required
- * in cases where the ContentResolver instance is a mock.
- * @hide
- */
- public static boolean sForceDefaults;
-
- /**
- * @return if the S notification rules should be backported to apps not yet targeting S
- * @hide
- */
- public static boolean shouldBackportSNotifRules(@NonNull ContentResolver contentResolver) {
- return false;
- }
-
- /**
- * @return the decoration type to be applied to notifications with fully custom view.
- * @hide
- */
- public static int getFullyCustomViewNotifDecoration(
- @NonNull ContentResolver contentResolver) {
- return DECORATION_MINIMAL;
- }
-
- /**
- * @return the decoration type to be applied to notifications with DecoratedCustomViewStyle.
- * @hide
- */
- public static int getDecoratedCustomViewNotifDecoration(
- @NonNull ContentResolver contentResolver) {
- return DECORATION_PARTIAL;
- }
- }
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 11adc5a..f4b9542 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1010,7 +1010,7 @@
* @deprecated Renamed to {@link #getCreatorPackage()}.
*/
@Deprecated
- @NonNull
+ @Nullable
public String getTargetPackage() {
return getCreatorPackage();
}
@@ -1032,7 +1032,7 @@
*
* @return The package name of the PendingIntent.
*/
- @NonNull
+ @Nullable
public String getCreatorPackage() {
return getCachedInfo().getCreatorPackage();
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3ef6757..6a71c92 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1962,14 +1962,20 @@
}
/**
- * Set the current zoom out level of the wallpaper
+ * Set the current zoom out level of the wallpaper.
+ *
+ * @param windowToken window requesting wallpaper zoom. Zoom level will only be applier while
+ * such window is visible.
* @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in
*
* @hide
*/
- public void setWallpaperZoomOut(IBinder windowToken, float zoom) {
+ public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
if (zoom < 0 || zoom > 1f) {
- throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom);
+ throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom);
+ }
+ if (windowToken == null) {
+ throw new IllegalArgumentException("windowToken must not be null");
}
try {
WindowManagerGlobal.getWindowSession().setWallpaperZoomOut(windowToken, zoom);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 426159f..117df02 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9990,26 +9990,24 @@
}
/**
- * Sets whether 5g slicing is enabled on the work profile.
+ * Sets whether enterprise network preference is enabled on the work profile.
*
- * Slicing allows operators to virtually divide their networks in portions and use different
- * portions for specific use cases; for example, a corporation can have a deal/agreement with
- * a carrier that all of its employees’ devices use data on a slice dedicated for enterprise
- * use.
+ * For example, a corporation can have a deal/agreement with a carrier that all of its
+ * employees’ devices use data on a network preference dedicated for enterprise use.
*
- * By default, 5g slicing is enabled on the work profile on supported carriers and devices.
- * Admins can explicitly disable it with this API.
+ * By default, enterprise network preference is enabled on the work profile on supported
+ * carriers and devices. Admins can explicitly disable it with this API.
*
* <p>This method can only be called by the profile owner of a managed profile.
*
- * @param enabled whether 5g Slice should be enabled.
+ * @param enabled whether enterprise network preference should be enabled.
* @throws SecurityException if the caller is not the profile owner.
**/
- public void setNetworkSlicingEnabled(boolean enabled) {
- throwIfParentInstance("setNetworkSlicingEnabled");
+ public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) {
+ throwIfParentInstance("setEnterpriseNetworkPreferenceEnabled");
if (mService != null) {
try {
- mService.setNetworkSlicingEnabled(enabled);
+ mService.setEnterpriseNetworkPreferenceEnabled(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10017,20 +10015,20 @@
}
/**
- * Indicates whether 5g slicing is enabled.
+ * Indicates whether whether enterprise network preference is enabled.
*
* <p>This method can be called by the profile owner of a managed profile.
*
- * @return whether 5g Slice is enabled.
+ * @return whether whether enterprise network preference is enabled.
* @throws SecurityException if the caller is not the profile owner.
*/
- public boolean isNetworkSlicingEnabled() {
- throwIfParentInstance("isNetworkSlicingEnabled");
+ public boolean isEnterpriseNetworkPreferenceEnabled() {
+ throwIfParentInstance("isEnterpriseNetworkPreferenceEnabled");
if (mService == null) {
return false;
}
try {
- return mService.isNetworkSlicingEnabled(myUserId());
+ return mService.isEnterpriseNetworkPreferenceEnabled(myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7901791..0ad92b7 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -268,8 +268,8 @@
void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
boolean isSecondaryLockscreenEnabled(in UserHandle userHandle);
- void setNetworkSlicingEnabled(in boolean enabled);
- boolean isNetworkSlicingEnabled(int userHandle);
+ void setEnterpriseNetworkPreferenceEnabled(in boolean enabled);
+ boolean isEnterpriseNetworkPreferenceEnabled(int userHandle);
void setLockTaskPackages(in ComponentName who, in String[] packages);
String[] getLockTaskPackages(in ComponentName who);
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index dd2ba7d..e645831 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -56,6 +56,7 @@
private CharSequence mNotificationContent;
private String mNotificationCategory;
private Uri mNotificationDataUri;
+ private int mMessagesCount;
private Intent mIntent;
private long mNotificationTimestamp;
private List<ConversationStatus> mStatuses;
@@ -74,6 +75,7 @@
mNotificationContent = b.mNotificationContent;
mNotificationCategory = b.mNotificationCategory;
mNotificationDataUri = b.mNotificationDataUri;
+ mMessagesCount = b.mMessagesCount;
mIntent = b.mIntent;
mNotificationTimestamp = b.mNotificationTimestamp;
mStatuses = b.mStatuses;
@@ -140,6 +142,10 @@
return mNotificationDataUri;
}
+ public int getMessagesCount() {
+ return mMessagesCount;
+ }
+
/**
* Provides an intent to launch. If present, we should manually launch the intent on tile
* click, rather than calling {@link android.content.pm.LauncherApps} to launch the shortcut ID.
@@ -175,6 +181,7 @@
builder.setNotificationContent(mNotificationContent);
builder.setNotificationCategory(mNotificationCategory);
builder.setNotificationDataUri(mNotificationDataUri);
+ builder.setMessagesCount(mMessagesCount);
builder.setIntent(mIntent);
builder.setNotificationTimestamp(mNotificationTimestamp);
builder.setStatuses(mStatuses);
@@ -196,6 +203,7 @@
private CharSequence mNotificationContent;
private String mNotificationCategory;
private Uri mNotificationDataUri;
+ private int mMessagesCount;
private Intent mIntent;
private long mNotificationTimestamp;
private List<ConversationStatus> mStatuses;
@@ -320,6 +328,12 @@
return this;
}
+ /** Sets the number of messages associated with the Tile. */
+ public Builder setMessagesCount(int messagesCount) {
+ mMessagesCount = messagesCount;
+ return this;
+ }
+
/** Sets an intent to launch on click. */
public Builder setIntent(Intent intent) {
mIntent = intent;
@@ -359,6 +373,7 @@
mNotificationContent = in.readCharSequence();
mNotificationCategory = in.readString();
mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader());
+ mMessagesCount = in.readInt();
mIntent = in.readParcelable(Intent.class.getClassLoader());
mNotificationTimestamp = in.readLong();
mStatuses = new ArrayList<>();
@@ -385,6 +400,7 @@
dest.writeCharSequence(mNotificationContent);
dest.writeString(mNotificationCategory);
dest.writeParcelable(mNotificationDataUri, flags);
+ dest.writeInt(mMessagesCount);
dest.writeParcelable(mIntent, flags);
dest.writeLong(mNotificationTimestamp);
dest.writeParcelableList(mStatuses, flags);
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index c8fa5c8..c71badb0 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -264,7 +264,7 @@
* See {@link ExternalTimeSuggestion} for more details.
* {@hide}
*/
- @RequiresPermission(android.Manifest.permission.SET_TIME)
+ @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME)
public void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) {
if (DEBUG) {
Log.d(TAG, "suggestExternalTime called: " + timeSuggestion);
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 5d50c5d7..ef92172 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -110,7 +110,9 @@
public long mTotalTimeForegroundServiceUsed;
/**
- * Last time this package's component is used, measured in milliseconds since the epoch.
+ * Last time this package's component is used by a client package, measured in milliseconds
+ * since the epoch. Note that component usage is only reported in certain cases (e.g. broadcast
+ * receiver, service, content provider).
* See {@link UsageEvents.Event#APP_COMPONENT_USED}
* @hide
*/
@@ -274,8 +276,10 @@
}
/**
- * Get the last time this package's component was used, measured in milliseconds since the
- * epoch.
+ * Get the last time this package's component was used by a client package, measured in
+ * milliseconds since the epoch. Note that component usage is only reported in certain cases
+ * (e.g. broadcast receiver, service, content provider).
+ * See {@link UsageEvents.Event#APP_COMPONENT_USED}
* @hide
*/
@SystemApi
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 8908649..a96c14f 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -26,6 +26,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PropertyInvalidatedCache;
+import android.companion.AssociationRequest;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
@@ -1231,8 +1232,7 @@
}
/**
- * Get the Bluetooth alias of the remote device.
- * <p>Alias is the locally modified name of a remote device.
+ * Get the locally modifiable name (alias) of the remote Bluetooth device.
*
* @return the Bluetooth alias, the friendly device name if no alias, or
* null if there was a problem
@@ -1258,25 +1258,35 @@
}
/**
- * Set the Bluetooth alias of the remote device.
- * <p>Alias is the locally modified name of a remote device.
- * <p>This methoid overwrites the alias. The changed
- * alias is saved in the local storage so that the change
- * is preserved over power cycle.
+ * Sets the locally modifiable name (alias) of the remote Bluetooth device. This method
+ * overwrites the previously stored alias. The new alias is saved in local
+ * storage so that the change is preserved over power cycles.
*
- * @return true on success, false on error
- * @hide
+ * <p>This method requires the calling app to be associated with Companion Device Manager (see
+ * {@link android.companion.CompanionDeviceManager#associate(AssociationRequest,
+ * android.companion.CompanionDeviceManager.Callback, Handler)}) and have the {@link
+ * android.Manifest.permission#BLUETOOTH} permission. Alternatively, if the caller has the
+ * {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission, they can bypass the
+ * Companion Device Manager association requirement.
+ *
+ * @param alias is the new locally modifiable name for the remote Bluetooth device which must be
+ * non-null and not the empty string.
+ * @return {@code true} if the alias is successfully set, {@code false} on error
+ * @throws IllegalArgumentException if the alias is {@code null} or the empty string
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH)
public boolean setAlias(@NonNull String alias) {
+ if (alias == null || alias.isEmpty()) {
+ throw new IllegalArgumentException("Cannot set the alias to null or the empty string");
+ }
final IBluetooth service = sService;
if (service == null) {
Log.e(TAG, "BT not enabled. Cannot set Remote Device name");
return false;
}
try {
- return service.setRemoteAlias(this, alias);
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return service.setRemoteAlias(this, alias, adapter.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 7459f87..27c579b 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -587,7 +587,7 @@
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
*/
public Builder setDeviceAddress(String deviceAddress) {
- return setDeviceAddress(mDeviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
+ return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
}
/**
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b441b36..86bd8a2 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -25,6 +25,7 @@
import android.app.Activity;
import android.app.Application;
import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
@@ -331,6 +332,33 @@
}
/**
+ * Checks whether the bluetooth device represented by the mac address was recently associated
+ * with the companion app. This allows these devices to skip the Bluetooth pairing dialog if
+ * their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}.
+ *
+ * @param packageName the package name of the calling app
+ * @param deviceMacAddress the bluetooth device's mac address
+ * @param userId the calling user's identifier
+ * @return true if it was recently associated and we can bypass the dialog, false otherwise
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
+ public boolean canPairWithoutPrompt(@NonNull String packageName,
+ @NonNull String deviceMacAddress, int userId) {
+ if (!checkFeaturePresent()) {
+ return false;
+ }
+ Objects.requireNonNull(packageName, "package name cannot be null");
+ Objects.requireNonNull(deviceMacAddress, "device mac address cannot be null");
+ try {
+ return mService.canPairWithoutPrompt(packageName, deviceMacAddress, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Register to receive callbacks whenever the associated device comes in and out of range.
*
* The provided device must be {@link #associate associated} with the calling app before
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 5af3b5a..c04d3be 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -40,6 +40,7 @@
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -327,6 +328,19 @@
return si;
}
+ /**
+ * @hide
+ */
+ @NonNull
+ public static List<GenericDocument> toGenericDocuments(
+ @NonNull final Collection<ShortcutInfo> shortcuts) {
+ final List<GenericDocument> docs = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfo si : shortcuts) {
+ docs.add(AppSearchShortcutInfo.instance(si));
+ }
+ return docs;
+ }
+
/** @hide */
@VisibleForTesting
public static class Builder extends GenericDocument.Builder<Builder> {
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1a5dad5..0da453d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1411,6 +1411,13 @@
private Boolean nativeHeapZeroInit;
/**
+ * If {@code true} this app requests optimized external storage access.
+ * The request may not be honored due to policy or other reasons.
+ */
+ @Nullable
+ private Boolean requestOptimizedExternalStorageAccess;
+
+ /**
* Represents the default policy. The actual policy used will depend on other properties of
* the application, e.g. the target SDK version.
* @hide
@@ -1566,6 +1573,10 @@
if (nativeHeapZeroInit != null) {
pw.println(prefix + "nativeHeapZeroInit=" + nativeHeapZeroInit);
}
+ if (requestOptimizedExternalStorageAccess != null) {
+ pw.println(prefix + "requestOptimizedExternalStorageAccess="
+ + requestOptimizedExternalStorageAccess);
+ }
}
super.dumpBack(pw, prefix);
}
@@ -1792,6 +1803,7 @@
gwpAsanMode = orig.gwpAsanMode;
memtagMode = orig.memtagMode;
nativeHeapZeroInit = orig.nativeHeapZeroInit;
+ requestOptimizedExternalStorageAccess = orig.requestOptimizedExternalStorageAccess;
}
public String toString() {
@@ -1880,6 +1892,7 @@
dest.writeInt(gwpAsanMode);
dest.writeInt(memtagMode);
sForBoolean.parcel(nativeHeapZeroInit, dest, parcelableFlags);
+ sForBoolean.parcel(requestOptimizedExternalStorageAccess, dest, parcelableFlags);
}
public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1965,6 +1978,7 @@
gwpAsanMode = source.readInt();
memtagMode = source.readInt();
nativeHeapZeroInit = sForBoolean.unparcel(source);
+ requestOptimizedExternalStorageAccess = sForBoolean.unparcel(source);
}
/**
@@ -2079,6 +2093,24 @@
}
/**
+ * @return
+ * <ul>
+ * <li>{@code true} if this app requested optimized external storage access
+ * <li>{@code false} if this app requests to disable optimized external storage access.
+ * <li>{@code null} if the app didn't specify
+ * {@link android.R.styleable#AndroidManifestApplication_requestOptimizedExternalStorageAccess}
+ * in its manifest file.
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public Boolean hasRequestOptimizedExternalStorageAccess() {
+ return requestOptimizedExternalStorageAccess;
+ }
+
+ /**
* If {@code true} this app allows heap pointer tagging.
*
* @hide
@@ -2351,6 +2383,10 @@
/** {@hide} */ public void setGwpAsanMode(@GwpAsanMode int value) { gwpAsanMode = value; }
/** {@hide} */ public void setMemtagMode(@MemtagMode int value) { memtagMode = value; }
/** {@hide} */ public void setNativeHeapZeroInit(@Nullable Boolean value) { nativeHeapZeroInit = value; }
+ /** {@hide} */
+ public void setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) {
+ requestOptimizedExternalStorageAccess = value;
+ }
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index cfb6e1b..5a89708 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -321,6 +321,10 @@
return UserManager.isUserTypeManagedProfile(userType);
}
+ public boolean isCloneProfile() {
+ return UserManager.isUserTypeCloneProfile(userType);
+ }
+
@UnsupportedAppUsage
public boolean isEnabled() {
return (flags & FLAG_DISABLED) != FLAG_DISABLED;
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index ba6416d..4dc9ce8 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -257,6 +257,9 @@
ParsingPackage setNativeHeapZeroInit(@Nullable Boolean nativeHeapZeroInit);
+ ParsingPackage setRequestOptimizedExternalStorageAccess(
+ @Nullable Boolean requestOptimizedExternalStorageAccess);
+
ParsingPackage setCrossProfile(boolean crossProfile);
ParsingPackage setFullBackupContent(int fullBackupContent);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index b3c26ab..065ed2e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -389,6 +389,10 @@
@DataClass.ParcelWith(ForBoolean.class)
private Boolean nativeHeapZeroInit;
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean requestOptimizedExternalStorageAccess;
+
// TODO(chiuwinson): Non-null
@Nullable
private ArraySet<String> mimeGroups;
@@ -1068,6 +1072,7 @@
appInfo.setGwpAsanMode(gwpAsanMode);
appInfo.setMemtagMode(memtagMode);
appInfo.setNativeHeapZeroInit(nativeHeapZeroInit);
+ appInfo.setRequestOptimizedExternalStorageAccess(requestOptimizedExternalStorageAccess);
appInfo.setBaseCodePath(mBaseApkPath);
appInfo.setBaseResourcePath(mBaseApkPath);
appInfo.setCodePath(mPath);
@@ -1203,6 +1208,7 @@
dest.writeMap(this.mProperties);
dest.writeInt(this.memtagMode);
sForBoolean.parcel(this.nativeHeapZeroInit, dest, flags);
+ sForBoolean.parcel(this.requestOptimizedExternalStorageAccess, dest, flags);
}
public ParsingPackageImpl(Parcel in) {
@@ -1326,6 +1332,7 @@
this.mProperties = in.createTypedArrayMap(Property.CREATOR);
this.memtagMode = in.readInt();
this.nativeHeapZeroInit = sForBoolean.unparcel(in);
+ this.requestOptimizedExternalStorageAccess = sForBoolean.unparcel(in);
assignDerivedFields();
}
@@ -2105,6 +2112,12 @@
return nativeHeapZeroInit;
}
+ @Nullable
+ @Override
+ public Boolean hasRequestOptimizedExternalStorageAccess() {
+ return requestOptimizedExternalStorageAccess;
+ }
+
@Override
public boolean isPartiallyDirectBootAware() {
return getBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE);
@@ -2555,6 +2568,11 @@
}
@Override
+ public ParsingPackageImpl setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) {
+ requestOptimizedExternalStorageAccess = value;
+ return this;
+ }
+ @Override
public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) {
return setBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE, value);
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 9f52183..47dfa9d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -902,6 +902,9 @@
@Nullable
Boolean isNativeHeapZeroInit();
+ @Nullable
+ Boolean hasRequestOptimizedExternalStorageAccess();
+
// TODO(b/135203078): Hide and enforce going through PackageInfoUtils
ApplicationInfo toAppInfoWithoutState();
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 2be0157..0e1574c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2012,6 +2012,12 @@
pkg.setNativeHeapZeroInit(sa.getBoolean(
R.styleable.AndroidManifestApplication_nativeHeapZeroInit, false));
}
+ if (sa.hasValue(
+ R.styleable.AndroidManifestApplication_requestOptimizedExternalStorageAccess)) {
+ pkg.setRequestOptimizedExternalStorageAccess(sa.getBoolean(R.styleable
+ .AndroidManifestApplication_requestOptimizedExternalStorageAccess,
+ false));
+ }
} finally {
sa.recycle();
}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index d187f60..d2d1441 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -16,6 +16,7 @@
package android.content.pm.verify.domain;
+import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,6 +30,8 @@
import android.os.ServiceSpecificException;
import android.os.UserHandle;
+import com.android.internal.util.CollectionUtils;
+
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -80,22 +83,6 @@
public static final int ERROR_DOMAIN_SET_ID_INVALID = 1;
/**
- * The provided domain set ID was null. This is a developer error.
- *
- * @hide
- */
- @SystemApi
- public static final int ERROR_DOMAIN_SET_ID_NULL = 2;
-
- /**
- * The provided set of domains was null or empty. This is a developer error.
- *
- * @hide
- */
- @SystemApi
- public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3;
-
- /**
* The provided set of domains contains a domain not declared by the target package. This
* usually means the work being processed by the verification agent is outdated and a new
* request should be scheduled, which should already be in progress as part of the
@@ -104,7 +91,7 @@
* @hide
*/
@SystemApi
- public static final int ERROR_UNKNOWN_DOMAIN = 4;
+ public static final int ERROR_UNKNOWN_DOMAIN = 2;
/**
* The system was unable to select the domain for approval. This indicates another application
@@ -114,17 +101,7 @@
* @hide
*/
@SystemApi
- public static final int ERROR_UNABLE_TO_APPROVE = 5;
-
- /**
- * The provided state code is incorrect. The domain verification agent is only allowed to
- * assign {@link DomainVerificationInfo#STATE_SUCCESS} or error codes equal to or greater than
- * {@link DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED}.
- *
- * @hide
- */
- @SystemApi
- public static final int ERROR_INVALID_STATE_CODE = 6;
+ public static final int ERROR_UNABLE_TO_APPROVE = 3;
/**
* Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API.
@@ -138,11 +115,8 @@
*/
@IntDef(prefix = {"ERROR_"}, value = {
ERROR_DOMAIN_SET_ID_INVALID,
- ERROR_DOMAIN_SET_ID_NULL,
- ERROR_DOMAIN_SET_NULL_OR_EMPTY,
ERROR_UNKNOWN_DOMAIN,
ERROR_UNABLE_TO_APPROVE,
- ERROR_INVALID_STATE_CODE
})
public @interface Error {
}
@@ -232,19 +206,21 @@
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains List of host names to change the state of.
* @param state See {@link DomainVerificationInfo#getHostToStateMap()}.
- * @throws NameNotFoundException If the ID is known to be good, but the package is
- * unavailable. This may be because the package is installed on
- * a volume that is no longer mounted. This error is
- * unrecoverable until the package is available again, and
- * should not be re-tried except on a time scheduled basis.
* @return error code or {@link #STATUS_OK} if successful
- *
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is installed on
+ * a volume that is no longer mounted. This error is
+ * unrecoverable until the package is available again, and
+ * should not be re-tried except on a time scheduled basis.
* @hide
*/
+ @CheckResult
@SystemApi
@RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
int state) throws NameNotFoundException {
+ validateInput(domainSetId, domains);
+
try {
return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
new DomainSet(domains), state);
@@ -312,19 +288,21 @@
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains The domains to toggle the state of.
* @param enabled Whether or not the app should automatically open the domains specified.
- * @throws NameNotFoundException If the ID is known to be good, but the package is
- * unavailable. This may be because the package is installed on
- * a volume that is no longer mounted. This error is
- * unrecoverable until the package is available again, and
- * should not be re-tried except on a time scheduled basis.
* @return error code or {@link #STATUS_OK} if successful
- *
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is installed on
+ * a volume that is no longer mounted. This error is
+ * unrecoverable until the package is available again, and
+ * should not be re-tried except on a time scheduled basis.
* @hide
*/
+ @CheckResult
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled) throws NameNotFoundException {
+ validateInput(domainSetId, domains);
+
try {
return mDomainVerificationManager.setDomainVerificationUserSelection(
domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId());
@@ -405,4 +383,12 @@
return exception;
}
}
+
+ private void validateInput(@Nullable UUID domainSetId, @Nullable Set<String> domains) {
+ if (domainSetId == null) {
+ throw new IllegalArgumentException("domainSetId cannot be null");
+ } else if (CollectionUtils.isEmpty(domains)) {
+ throw new IllegalArgumentException("Provided domain set cannot be empty");
+ }
+ }
}
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 6a3cb42..5b79f73 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -29,7 +29,7 @@
*/
interface IVcnManagementService {
void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName);
- void clearVcnConfig(in ParcelUuid subscriptionGroup);
+ void clearVcnConfig(in ParcelUuid subscriptionGroup, in String opPkgName);
void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index b73fdbf..abd41da 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -154,7 +154,7 @@
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
try {
- mService.clearVcnConfig(subscriptionGroup);
+ mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName());
} catch (ServiceSpecificException e) {
throw new IOException(e);
} catch (RemoteException e) {
@@ -439,7 +439,7 @@
* @param statusCode the code for the status change encountered by this {@link
* VcnStatusCallback}'s subscription group.
*/
- public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode);
+ public abstract void onStatusChanged(@VcnStatusCode int statusCode);
/**
* Invoked when a VCN Gateway Connection corresponding to this callback's subscription group
@@ -476,7 +476,7 @@
* and there is a VCN active for its specified subscription group (this may happen after the
* callback is registered).
*
- * <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the
+ * <p>{@link VcnStatusCallback#onStatusChanged(int)} will be invoked on registration with the
* current status for the specified subscription group's VCN. If the registrant is not
* privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be
* returned.
@@ -580,7 +580,7 @@
@Override
public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode)));
+ () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode)));
}
// TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling'
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index f12eb88..a0721c3 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -34,7 +34,7 @@
public final class BatteryUsageStats implements Parcelable {
private final double mConsumedPower;
private final int mDischargePercentage;
- private final long mStatsStartRealtimeMs;
+ private final long mStatsStartTimestampMs;
private final double mDischargedPowerLowerBound;
private final double mDischargedPowerUpperBound;
private final long mBatteryTimeRemainingMs;
@@ -46,7 +46,7 @@
private final List<BatteryStats.HistoryTag> mHistoryTagPool;
private BatteryUsageStats(@NonNull Builder builder) {
- mStatsStartRealtimeMs = builder.mStatsStartRealtimeMs;
+ mStatsStartTimestampMs = builder.mStatsStartTimestampMs;
mDischargePercentage = builder.mDischargePercentage;
mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah;
mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
@@ -91,10 +91,11 @@
}
/**
- * Timestamp of the latest battery stats reset, in milliseconds.
+ * Timestamp (as returned by System.currentTimeMillis()) of the latest battery stats reset, in
+ * milliseconds.
*/
- public long getStatsStartRealtime() {
- return mStatsStartRealtimeMs;
+ public long getStatsStartTimestamp() {
+ return mStatsStartTimestampMs;
}
/**
@@ -174,7 +175,7 @@
}
private BatteryUsageStats(@NonNull Parcel source) {
- mStatsStartRealtimeMs = source.readLong();
+ mStatsStartTimestampMs = source.readLong();
mConsumedPower = source.readDouble();
mDischargePercentage = source.readInt();
mDischargedPowerLowerBound = source.readDouble();
@@ -214,7 +215,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeLong(mStatsStartRealtimeMs);
+ dest.writeLong(mStatsStartTimestampMs);
dest.writeDouble(mConsumedPower);
dest.writeInt(mDischargePercentage);
dest.writeDouble(mDischargedPowerLowerBound);
@@ -260,7 +261,7 @@
public static final class Builder {
private final int mCustomPowerComponentCount;
private final int mCustomTimeComponentCount;
- private long mStatsStartRealtimeMs;
+ private long mStatsStartTimestampMs;
private int mDischargePercentage;
private double mDischargedPowerLowerBoundMah;
private double mDischargedPowerUpperBoundMah;
@@ -291,8 +292,8 @@
/**
* Sets the timestamp of the latest battery stats reset, in milliseconds.
*/
- public Builder setStatsStartRealtime(long statsStartRealtimeMs) {
- mStatsStartRealtimeMs = statsStartRealtimeMs;
+ public Builder setStatsStartTimestamp(long statsStartTimestampMs) {
+ mStatsStartTimestampMs = statsStartTimestampMs;
return this;
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 087568d..34f2c103f 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -99,6 +99,8 @@
boolean someUserHasSeedAccount(in String accountName, in String accountType);
boolean isProfile(int userId);
boolean isManagedProfile(int userId);
+ boolean isCloneProfile(int userId);
+ boolean sharesMediaWithParent(int userId);
boolean isDemoUser(int userId);
boolean isPreCreated(int userId);
UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags,
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index b42a495..219912c 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -218,7 +218,7 @@
@Override
public boolean[] arePrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
boolean[] supported = new boolean[primitiveIds.length];
if (mVibratorManager == null) {
Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index b528eb1..841aad5 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -211,7 +211,7 @@
@Override
public boolean[] arePrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
boolean[] supported = new boolean[primitiveIds.length];
for (int i = 0; i < primitiveIds.length; i++) {
supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 5069e031..d4de4fa 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -25,6 +25,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
+import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -136,6 +137,16 @@
public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
/**
+ * User type representing a clone profile. Clone profile is a user profile type used to run
+ * second instance of an otherwise single user App (eg, messengers). Only the primary user
+ * is allowed to have a clone profile.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
+
+ /**
* User type representing a generic profile for testing purposes. Only on debuggable builds.
* @hide
*/
@@ -1984,6 +1995,14 @@
}
/**
+ * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
+ * @hide
+ */
+ public static boolean isUserTypeCloneProfile(String userType) {
+ return USER_TYPE_PROFILE_CLONE.equals(userType);
+ }
+
+ /**
* Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
* user type.
* @hide
@@ -2233,6 +2252,31 @@
}
/**
+ * Checks if the context user is a clone profile.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
+ * must be in the same profile group of the user.
+ *
+ * @return whether the context user is a clone profile.
+ *
+ * @see android.os.UserManager#USER_TYPE_PROFILE_CLONE
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @UserHandleAware
+ @SuppressAutoDoc
+ public boolean isCloneProfile() {
+ try {
+ return mService.isCloneProfile(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if the calling app is running as an ephemeral user.
*
* @return whether the caller is an ephemeral user.
@@ -4064,6 +4108,31 @@
}
/**
+ * If the user is a {@link UserManager#isProfile profile}, checks if the user
+ * shares media with its parent user (the user that created this profile).
+ * Returns false for any other type of user.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the
+ * caller must be in the same profile group as the user.
+ *
+ * @return true if the user shares media with its parent user, false otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @UserHandleAware
+ @SuppressAutoDoc
+ public boolean sharesMediaWithParent() {
+ try {
+ return mService.sharesMediaWithParent(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes a user and all associated data.
* @param userId the integer handle of the user.
* @hide
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 217f178..cec323f 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -21,6 +21,8 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.media.AudioAttributes;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import java.lang.annotation.Retention;
@@ -330,9 +332,9 @@
private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) {
if (effect != null) {
- if (mUsage == USAGE_UNKNOWN && effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- switch (prebaked.getId()) {
+ PrebakedSegment prebaked = extractPrebakedSegment(effect);
+ if (mUsage == USAGE_UNKNOWN && prebaked != null) {
+ switch (prebaked.getEffectId()) {
case VibrationEffect.EFFECT_CLICK:
case VibrationEffect.EFFECT_DOUBLE_CLICK:
case VibrationEffect.EFFECT_HEAVY_CLICK:
@@ -355,6 +357,20 @@
}
}
+ @Nullable
+ private PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.Composed) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ if (composed.getSegments().size() == 1) {
+ VibrationEffectSegment segment = composed.getSegments().get(0);
+ if (segment instanceof PrebakedSegment) {
+ return (PrebakedSegment) segment;
+ }
+ }
+ }
+ return null;
+ }
+
private void setUsage(@NonNull AudioAttributes audio) {
mOriginalAudioUsage = audio.getUsage();
switch (audio.getUsage()) {
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
index 89478fa..6311760 100644
--- a/core/java/android/os/VibrationEffect.aidl
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -17,4 +17,3 @@
package android.os;
parcelable VibrationEffect;
-parcelable VibrationEffect.Composition.PrimitiveEffect;
\ No newline at end of file
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0199fad..c78bf8c 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -21,6 +21,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
@@ -28,6 +29,11 @@
import android.hardware.vibrator.V1_0.EffectStrength;
import android.hardware.vibrator.V1_3.Effect;
import android.net.Uri;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.MathUtils;
import com.android.internal.util.Preconditions;
@@ -45,11 +51,6 @@
* These effects may be any number of things, from single shot vibrations to complex waveforms.
*/
public abstract class VibrationEffect implements Parcelable {
- private static final int PARCEL_TOKEN_ONE_SHOT = 1;
- private static final int PARCEL_TOKEN_WAVEFORM = 2;
- private static final int PARCEL_TOKEN_EFFECT = 3;
- private static final int PARCEL_TOKEN_COMPOSITION = 4;
-
// Stevens' coefficient to scale the perceived vibration intensity.
private static final float SCALE_GAMMA = 0.65f;
@@ -181,9 +182,7 @@
* @return The desired effect.
*/
public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
- VibrationEffect effect = new OneShot(milliseconds, amplitude);
- effect.validate();
- return effect;
+ return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */);
}
/**
@@ -243,7 +242,19 @@
* @return The desired effect.
*/
public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
- VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+ if (timings.length != amplitudes.length) {
+ throw new IllegalArgumentException(
+ "timing and amplitude arrays must be of equal length"
+ + " (timings.length=" + timings.length
+ + ", amplitudes.length=" + amplitudes.length + ")");
+ }
+ List<StepSegment> segments = new ArrayList<>();
+ for (int i = 0; i < timings.length; i++) {
+ float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE
+ ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE;
+ segments.add(new StepSegment(parsedAmplitude, /* frequency= */ 0, (int) timings[i]));
+ }
+ VibrationEffect effect = new Composed(segments, repeat);
effect.validate();
return effect;
}
@@ -317,7 +328,8 @@
*/
@TestApi
public static VibrationEffect get(int effectId, boolean fallback) {
- VibrationEffect effect = new Prebaked(effectId, fallback, EffectStrength.MEDIUM);
+ VibrationEffect effect = new Composed(
+ new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM));
effect.validate();
return effect;
}
@@ -379,8 +391,26 @@
* @see VibrationEffect.Composition
*/
@NonNull
- public static VibrationEffect.Composition startComposition() {
- return new VibrationEffect.Composition();
+ public static Composition startComposition() {
+ return new Composition();
+ }
+
+ /**
+ * Start building a waveform vibration.
+ *
+ * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
+ * control over vibration frequency and ramping up or down the vibration amplitude, frequency or
+ * both.
+ *
+ * <p>For simpler waveform patterns see {@link #createWaveform} methods.
+ *
+ * @hide
+ * @see VibrationEffect.WaveformBuilder
+ */
+ @TestApi
+ @NonNull
+ public static WaveformBuilder startWaveform() {
+ return new WaveformBuilder();
}
@Override
@@ -428,32 +458,28 @@
public abstract <T extends VibrationEffect> T scale(float scaleFactor);
/**
- * Scale given vibration intensity by the given factor.
+ * Applies given effect strength to prebaked effects represented by one of
+ * VibrationEffect.EFFECT_*.
*
- * @param amplitude amplitude of the effect, must be between 0 and MAX_AMPLITUDE
- * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
- * scale down the intensity, values larger than 1 will scale up
- *
+ * @param effectStrength new effect strength to be applied, one of
+ * VibrationEffect.EFFECT_STRENGTH_*.
+ * @return this if there is no change to this effect, or a copy of this effect with applied
+ * effect strength otherwise.
* @hide
*/
- protected static int scale(int amplitude, float scaleFactor) {
- if (amplitude == 0) {
- return 0;
- }
- int scaled = (int) (scale((float) amplitude / MAX_AMPLITUDE, scaleFactor) * MAX_AMPLITUDE);
- return MathUtils.constrain(scaled, 1, MAX_AMPLITUDE);
+ public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) {
+ return (T) this;
}
/**
* Scale given vibration intensity by the given factor.
*
- * @param intensity relative intensity of the effect, must be between 0 and 1
+ * @param intensity relative intensity of the effect, must be between 0 and 1
* @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
* scale down the intensity, values larger than 1 will scale up
- *
* @hide
*/
- protected static float scale(float intensity, float scaleFactor) {
+ public static float scale(float intensity, float scaleFactor) {
// Applying gamma correction to the scale factor, which is the same as encoding the input
// value, scaling it, then decoding the scaled value.
float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
@@ -516,545 +542,152 @@
}
}
- /** @hide */
+ /**
+ * Implementation of {@link VibrationEffect} described by a composition of one or more
+ * {@link VibrationEffectSegment}, with an optional index to represent repeating effects.
+ *
+ * @hide
+ */
@TestApi
- public static class OneShot extends VibrationEffect implements Parcelable {
- private final long mDuration;
- private final int mAmplitude;
+ public static final class Composed extends VibrationEffect {
+ private final ArrayList<VibrationEffectSegment> mSegments;
+ private final int mRepeatIndex;
- public OneShot(Parcel in) {
- mDuration = in.readLong();
- mAmplitude = in.readInt();
+ Composed(@NonNull Parcel in) {
+ this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt());
}
- public OneShot(long milliseconds, int amplitude) {
- mDuration = milliseconds;
- mAmplitude = amplitude;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- public int getAmplitude() {
- return mAmplitude;
+ Composed(@NonNull VibrationEffectSegment segment) {
+ this(Arrays.asList(segment), /* repeatIndex= */ -1);
}
/** @hide */
- @Override
- public OneShot scale(float scaleFactor) {
- if (scaleFactor == 1f || mAmplitude == DEFAULT_AMPLITUDE) {
- // Just return this if there's no scaling to be done or if amplitude is not yet set.
- return this;
- }
- return new OneShot(mDuration, scale(mAmplitude, scaleFactor));
+ public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) {
+ super();
+ mSegments = new ArrayList<>(segments);
+ mRepeatIndex = repeatIndex;
}
- /** @hide */
- @Override
- public OneShot resolve(int defaultAmplitude) {
- if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude <= 0) {
- throw new IllegalArgumentException(
- "amplitude must be between 1 and 255 inclusive (amplitude="
- + defaultAmplitude + ")");
- }
- if (mAmplitude == DEFAULT_AMPLITUDE) {
- return new OneShot(mDuration, defaultAmplitude);
- }
- return this;
- }
-
- /** @hide */
- @Override
- public void validate() {
- if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
- throw new IllegalArgumentException(
- "amplitude must either be DEFAULT_AMPLITUDE, "
- + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
- }
- if (mDuration <= 0) {
- throw new IllegalArgumentException(
- "duration must be positive (duration=" + mDuration + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.OneShot)) {
- return false;
- }
- VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
- return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result += 37 * (int) mDuration;
- result += 37 * mAmplitude;
- return result;
- }
-
- @Override
- public String toString() {
- return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_ONE_SHOT);
- out.writeLong(mDuration);
- out.writeInt(mAmplitude);
- }
-
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
- new Parcelable.Creator<OneShot>() {
- @Override
- public OneShot createFromParcel(Parcel in) {
- // Skip the type token
- in.readInt();
- return new OneShot(in);
- }
- @Override
- public OneShot[] newArray(int size) {
- return new OneShot[size];
- }
- };
- }
-
- /** @hide */
- @TestApi
- public static class Waveform extends VibrationEffect implements Parcelable {
- private final long[] mTimings;
- private final int[] mAmplitudes;
- private final int mRepeat;
-
- public Waveform(Parcel in) {
- this(in.createLongArray(), in.createIntArray(), in.readInt());
- }
-
- public Waveform(long[] timings, int[] amplitudes, int repeat) {
- mTimings = new long[timings.length];
- System.arraycopy(timings, 0, mTimings, 0, timings.length);
- mAmplitudes = new int[amplitudes.length];
- System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
- mRepeat = repeat;
- }
-
- public long[] getTimings() {
- return mTimings;
- }
-
- public int[] getAmplitudes() {
- return mAmplitudes;
+ @NonNull
+ public List<VibrationEffectSegment> getSegments() {
+ return mSegments;
}
public int getRepeatIndex() {
- return mRepeat;
+ return mRepeatIndex;
+ }
+
+ @Override
+ public void validate() {
+ int segmentCount = mSegments.size();
+ boolean hasNonZeroDuration = false;
+ boolean hasNonZeroAmplitude = false;
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = mSegments.get(i);
+ segment.validate();
+ // A segment with unknown duration = -1 still counts as a non-zero duration.
+ hasNonZeroDuration |= segment.getDuration() != 0;
+ hasNonZeroAmplitude |= segment.hasNonZeroAmplitude();
+ }
+ if (!hasNonZeroDuration) {
+ throw new IllegalArgumentException("at least one timing must be non-zero"
+ + " (segments=" + mSegments + ")");
+ }
+ if (!hasNonZeroAmplitude) {
+ throw new IllegalArgumentException("at least one amplitude must be non-zero"
+ + " (segments=" + mSegments + ")");
+ }
+ if (mRepeatIndex != -1) {
+ Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1,
+ "repeat index must be within the bounds of the segments (segments.length="
+ + segmentCount + ", index=" + mRepeatIndex + ")");
+ }
}
@Override
public long getDuration() {
- if (mRepeat >= 0) {
+ if (mRepeatIndex >= 0) {
return Long.MAX_VALUE;
}
- long duration = 0;
- for (long d : mTimings) {
- duration += d;
- }
- return duration;
- }
-
- /** @hide */
- @Override
- public Waveform scale(float scaleFactor) {
- if (scaleFactor == 1f) {
- // Just return this if there's no scaling to be done.
- return this;
- }
- boolean scaled = false;
- int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
- for (int i = 0; i < scaledAmplitudes.length; i++) {
- if (scaledAmplitudes[i] == DEFAULT_AMPLITUDE) {
- // Skip amplitudes that are not set.
- continue;
+ int segmentCount = mSegments.size();
+ long totalDuration = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ long segmentDuration = mSegments.get(i).getDuration();
+ if (segmentDuration < 0) {
+ return segmentDuration;
}
- scaled = true;
- scaledAmplitudes[i] = scale(scaledAmplitudes[i], scaleFactor);
+ totalDuration += segmentDuration;
}
- if (!scaled) {
- // Just return this if no scaling was done.
- return this;
- }
- return new Waveform(mTimings, scaledAmplitudes, mRepeat);
+ return totalDuration;
}
- /** @hide */
- @Override
- public Waveform resolve(int defaultAmplitude) {
- if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
- throw new IllegalArgumentException(
- "Amplitude is negative or greater than MAX_AMPLITUDE");
- }
- boolean resolved = false;
- int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
- for (int i = 0; i < resolvedAmplitudes.length; i++) {
- if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
- resolvedAmplitudes[i] = defaultAmplitude;
- resolved = true;
- }
- }
- if (!resolved) {
- return this;
- }
- return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
- }
-
- /** @hide */
- @Override
- public void validate() {
- if (mTimings.length != mAmplitudes.length) {
- throw new IllegalArgumentException(
- "timing and amplitude arrays must be of equal length"
- + " (timings.length=" + mTimings.length
- + ", amplitudes.length=" + mAmplitudes.length + ")");
- }
- if (!hasNonZeroEntry(mTimings)) {
- throw new IllegalArgumentException("at least one timing must be non-zero"
- + " (timings=" + Arrays.toString(mTimings) + ")");
- }
- for (long timing : mTimings) {
- if (timing < 0) {
- throw new IllegalArgumentException("timings must all be >= 0"
- + " (timings=" + Arrays.toString(mTimings) + ")");
- }
- }
- for (int amplitude : mAmplitudes) {
- if (amplitude < -1 || amplitude > 255) {
- throw new IllegalArgumentException(
- "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
- + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
- }
- }
- if (mRepeat < -1 || mRepeat >= mTimings.length) {
- throw new IllegalArgumentException(
- "repeat index must be within the bounds of the timings array"
- + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.Waveform)) {
- return false;
- }
- VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
- return Arrays.equals(mTimings, other.mTimings)
- && Arrays.equals(mAmplitudes, other.mAmplitudes)
- && mRepeat == other.mRepeat;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result += 37 * Arrays.hashCode(mTimings);
- result += 37 * Arrays.hashCode(mAmplitudes);
- result += 37 * mRepeat;
- return result;
- }
-
- @Override
- public String toString() {
- return "Waveform{mTimings=" + Arrays.toString(mTimings)
- + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
- + ", mRepeat=" + mRepeat
- + "}";
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_WAVEFORM);
- out.writeLongArray(mTimings);
- out.writeIntArray(mAmplitudes);
- out.writeInt(mRepeat);
- }
-
- private static boolean hasNonZeroEntry(long[] vals) {
- for (long val : vals) {
- if (val != 0) {
- return true;
- }
- }
- return false;
- }
-
-
- public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
- new Parcelable.Creator<Waveform>() {
- @Override
- public Waveform createFromParcel(Parcel in) {
- // Skip the type token
- in.readInt();
- return new Waveform(in);
- }
- @Override
- public Waveform[] newArray(int size) {
- return new Waveform[size];
- }
- };
- }
-
- /** @hide */
- @TestApi
- public static class Prebaked extends VibrationEffect implements Parcelable {
- private final int mEffectId;
- private final boolean mFallback;
- private final int mEffectStrength;
- @Nullable
- private final VibrationEffect mFallbackEffect;
-
- public Prebaked(Parcel in) {
- mEffectId = in.readInt();
- mFallback = in.readByte() != 0;
- mEffectStrength = in.readInt();
- mFallbackEffect = in.readParcelable(VibrationEffect.class.getClassLoader());
- }
-
- public Prebaked(int effectId, boolean fallback, int effectStrength) {
- mEffectId = effectId;
- mFallback = fallback;
- mEffectStrength = effectStrength;
- mFallbackEffect = null;
- }
-
- /** @hide */
- public Prebaked(int effectId, int effectStrength, @NonNull VibrationEffect fallbackEffect) {
- mEffectId = effectId;
- mFallback = true;
- mEffectStrength = effectStrength;
- mFallbackEffect = fallbackEffect;
- }
-
- public int getId() {
- return mEffectId;
- }
-
- /**
- * Whether the effect should fall back to a generic pattern if there's no hardware specific
- * implementation of it.
- */
- public boolean shouldFallback() {
- return mFallback;
- }
-
- @Override
- public long getDuration() {
- return -1;
- }
-
- /** @hide */
- @Override
- public Prebaked resolve(int defaultAmplitude) {
- if (mFallbackEffect != null) {
- VibrationEffect resolvedFallback = mFallbackEffect.resolve(defaultAmplitude);
- if (!mFallbackEffect.equals(resolvedFallback)) {
- return new Prebaked(mEffectId, mEffectStrength, resolvedFallback);
- }
- }
- return this;
- }
-
- /** @hide */
- @Override
- public Prebaked scale(float scaleFactor) {
- if (mFallbackEffect != null) {
- VibrationEffect scaledFallback = mFallbackEffect.scale(scaleFactor);
- if (!mFallbackEffect.equals(scaledFallback)) {
- return new Prebaked(mEffectId, mEffectStrength, scaledFallback);
- }
- }
- // Prebaked effect strength cannot be scaled with this method.
- return this;
- }
-
- /**
- * Set the effect strength.
- */
- public int getEffectStrength() {
- return mEffectStrength;
- }
-
- /**
- * Return the fallback effect, if set.
- *
- * @hide
- */
- @Nullable
- public VibrationEffect getFallbackEffect() {
- return mFallbackEffect;
- }
-
- private static boolean isValidEffectStrength(int strength) {
- switch (strength) {
- case EffectStrength.LIGHT:
- case EffectStrength.MEDIUM:
- case EffectStrength.STRONG:
- return true;
- default:
- return false;
- }
- }
-
- /** @hide */
- @Override
- public void validate() {
- switch (mEffectId) {
- case EFFECT_CLICK:
- case EFFECT_DOUBLE_CLICK:
- case EFFECT_TICK:
- case EFFECT_TEXTURE_TICK:
- case EFFECT_THUD:
- case EFFECT_POP:
- case EFFECT_HEAVY_CLICK:
- break;
- default:
- if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
- throw new IllegalArgumentException(
- "Unknown prebaked effect type (value=" + mEffectId + ")");
- }
- }
- if (!isValidEffectStrength(mEffectStrength)) {
- throw new IllegalArgumentException(
- "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.Prebaked)) {
- return false;
- }
- VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
- return mEffectId == other.mEffectId
- && mFallback == other.mFallback
- && mEffectStrength == other.mEffectStrength
- && Objects.equals(mFallbackEffect, other.mFallbackEffect);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mEffectId, mFallback, mEffectStrength, mFallbackEffect);
- }
-
- @Override
- public String toString() {
- return "Prebaked{mEffectId=" + effectIdToString(mEffectId)
- + ", mEffectStrength=" + effectStrengthToString(mEffectStrength)
- + ", mFallback=" + mFallback
- + ", mFallbackEffect=" + mFallbackEffect
- + "}";
- }
-
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_EFFECT);
- out.writeInt(mEffectId);
- out.writeByte((byte) (mFallback ? 1 : 0));
- out.writeInt(mEffectStrength);
- out.writeParcelable(mFallbackEffect, flags);
- }
-
- public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
- new Parcelable.Creator<Prebaked>() {
- @Override
- public Prebaked createFromParcel(Parcel in) {
- // Skip the type token
- in.readInt();
- return new Prebaked(in);
- }
- @Override
- public Prebaked[] newArray(int size) {
- return new Prebaked[size];
- }
- };
- }
-
- /** @hide */
- public static final class Composed extends VibrationEffect implements Parcelable {
- private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects;
-
- /**
- * @hide
- */
- @SuppressWarnings("unchecked")
- public Composed(@NonNull Parcel in) {
- this(in.readArrayList(Composed.class.getClassLoader()));
- }
-
- /**
- * @hide
- */
- public Composed(List<Composition.PrimitiveEffect> effects) {
- mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects));
- }
-
- /**
- * @hide
- */
@NonNull
- public List<Composition.PrimitiveEffect> getPrimitiveEffects() {
- return mPrimitiveEffects;
- }
-
@Override
- public long getDuration() {
- return -1;
+ public Composed resolve(int defaultAmplitude) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
+ }
+ if (resolvedSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
+ resolved.validate();
+ return resolved;
}
- /** @hide */
- @Override
- public VibrationEffect resolve(int defaultAmplitude) {
- // Primitive effects already have default primitive intensity set, so ignore this.
- return this;
- }
-
- /** @hide */
+ @NonNull
@Override
public Composed scale(float scaleFactor) {
- if (scaleFactor == 1f) {
- // Just return this if there's no scaling to be done.
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).scale(scaleFactor));
+ }
+ if (scaledSegments.equals(mSegments)) {
return this;
}
- final int primitiveCount = mPrimitiveEffects.size();
- List<Composition.PrimitiveEffect> scaledPrimitives = new ArrayList<>();
- for (int i = 0; i < primitiveCount; i++) {
- Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i);
- scaledPrimitives.add(new Composition.PrimitiveEffect(
- primitive.id, scale(primitive.scale, scaleFactor), primitive.delay));
- }
- return new Composed(scaledPrimitives);
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
}
- /** @hide */
+ @NonNull
@Override
- public void validate() {
- final int primitiveCount = mPrimitiveEffects.size();
- for (int i = 0; i < primitiveCount; i++) {
- Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i);
- Composition.checkPrimitive(primitive.id);
- Preconditions.checkArgumentInRange(primitive.scale, 0.0f, 1.0f, "scale");
- Preconditions.checkArgumentNonNegative(primitive.delay,
- "Primitive delay must be zero or positive");
+ public Composed applyEffectStrength(int effectStrength) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength));
}
+ if (scaledSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
}
@Override
- public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_COMPOSITION);
- out.writeList(mPrimitiveEffects);
+ public boolean equals(@Nullable Object o) {
+ if (!(o instanceof Composed)) {
+ return false;
+ }
+ Composed other = (Composed) o;
+ return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSegments, mRepeatIndex);
+ }
+
+ @Override
+ public String toString() {
+ return "Composed{segments=" + mSegments
+ + ", repeat=" + mRepeatIndex
+ + "}";
}
@Override
@@ -1063,34 +696,20 @@
}
@Override
- public boolean equals(@Nullable Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Composed composed = (Composed) o;
- return mPrimitiveEffects.equals(composed.mPrimitiveEffects);
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeList(mSegments);
+ out.writeInt(mRepeatIndex);
}
- @Override
- public int hashCode() {
- return Objects.hash(mPrimitiveEffects);
- }
-
- @Override
- public String toString() {
- return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}';
- }
-
- public static final @NonNull Parcelable.Creator<Composed> CREATOR =
- new Parcelable.Creator<Composed>() {
+ @NonNull
+ public static final Creator<Composed> CREATOR =
+ new Creator<Composed>() {
@Override
- public Composed createFromParcel(@NonNull Parcel in) {
- // Skip the type token
- in.readInt();
+ public Composed createFromParcel(Parcel in) {
return new Composed(in);
}
@Override
- @NonNull
public Composed[] newArray(int size) {
return new Composed[size];
}
@@ -1115,7 +734,7 @@
PRIMITIVE_LOW_TICK,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Primitive {}
+ public @interface PrimitiveType {}
/**
* No haptic effect. Used to generate extended delays between primitives.
@@ -1166,9 +785,46 @@
public static final int PRIMITIVE_LOW_TICK = 8;
- private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
+ private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+ private int mRepeatIndex = -1;
- Composition() { }
+ Composition() {}
+
+ /**
+ * Add a haptic effect to the end of the current composition.
+ *
+ * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied.
+ *
+ * @param effect The effect to add to this composition as a primitive
+ * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Composition addEffect(@NonNull VibrationEffect effect) {
+ return addEffect(effect, /* delay= */ 0);
+ }
+
+ /**
+ * Add a haptic effect to the end of the current composition.
+ *
+ * @param effect The effect to add to this composition as a primitive
+ * @param delay The amount of time in milliseconds to wait before playing this primitive
+ * @return The {@link Composition} object to enable adding multiple primitives in one chain.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Composition addEffect(@NonNull VibrationEffect effect,
+ @IntRange(from = 0) int delay) {
+ Preconditions.checkArgumentNonnegative(delay);
+ if (delay > 0) {
+ // Created a segment sustaining the zero amplitude to represent the delay.
+ addSegment(new StepSegment(/* amplitude= */ 0, /* frequency= */ 0,
+ /* duration= */ delay));
+ }
+ return addSegments(effect);
+ }
/**
* Add a haptic primitive to the end of the current composition.
@@ -1181,9 +837,8 @@
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
*/
@NonNull
- public Composition addPrimitive(@Primitive int primitiveId) {
- addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
- return this;
+ public Composition addPrimitive(@PrimitiveType int primitiveId) {
+ return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
}
/**
@@ -1197,10 +852,9 @@
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
*/
@NonNull
- public Composition addPrimitive(@Primitive int primitiveId,
+ public Composition addPrimitive(@PrimitiveType int primitiveId,
@FloatRange(from = 0f, to = 1f) float scale) {
- addPrimitive(primitiveId, scale, /*delay*/ 0);
- return this;
+ return addPrimitive(primitiveId, scale, /*delay*/ 0);
}
/**
@@ -1213,9 +867,36 @@
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
*/
@NonNull
- public Composition addPrimitive(@Primitive int primitiveId,
+ public Composition addPrimitive(@PrimitiveType int primitiveId,
@FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
- mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay));
+ PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale,
+ delay);
+ primitive.validate();
+ return addSegment(primitive);
+ }
+
+ private Composition addSegment(VibrationEffectSegment segment) {
+ if (mRepeatIndex >= 0) {
+ throw new IllegalStateException(
+ "Composition already have a repeating effect so any new primitive would be"
+ + " unreachable.");
+ }
+ mSegments.add(segment);
+ return this;
+ }
+
+ private Composition addSegments(VibrationEffect effect) {
+ if (mRepeatIndex >= 0) {
+ throw new IllegalStateException(
+ "Composition already have a repeating effect so any new primitive would be"
+ + " unreachable.");
+ }
+ Composed composed = (Composed) effect;
+ if (composed.getRepeatIndex() >= 0) {
+ // Start repeating from the index relative to the composed waveform.
+ mRepeatIndex = mSegments.size() + composed.getRepeatIndex();
+ }
+ mSegments.addAll(composed.getSegments());
return this;
}
@@ -1230,22 +911,13 @@
*/
@NonNull
public VibrationEffect compose() {
- if (mEffects.isEmpty()) {
+ if (mSegments.isEmpty()) {
throw new IllegalStateException(
"Composition must have at least one element to compose.");
}
- return new VibrationEffect.Composed(mEffects);
- }
-
- /**
- * @throws IllegalArgumentException throws if the primitive ID is not within the valid range
- * @hide
- *
- */
- static int checkPrimitive(int primitiveId) {
- Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LOW_TICK,
- "primitiveId");
- return primitiveId;
+ VibrationEffect effect = new Composed(mSegments, mRepeatIndex);
+ effect.validate();
+ return effect;
}
/**
@@ -1254,7 +926,7 @@
* @return The ID in a human readable format.
* @hide
*/
- public static String primitiveToString(@Primitive int id) {
+ public static String primitiveToString(@PrimitiveType int id) {
switch (id) {
case PRIMITIVE_NOOP:
return "PRIMITIVE_NOOP";
@@ -1278,90 +950,172 @@
return Integer.toString(id);
}
}
+ }
+ /**
+ * A builder for waveform haptic effects.
+ *
+ * <p>Waveform vibrations constitute of one or more timed segments where the vibration
+ * amplitude, frequency or both can linearly ramp to new values.
+ *
+ * <p>Waveform segments may have zero duration, which represent a jump to new vibration
+ * amplitude and/or frequency values.
+ *
+ * <p>Waveform segments may have the same start and end vibration amplitude and frequency,
+ * which represent a step where the amplitude and frequency are maintained for that duration.
+ *
+ * @hide
+ * @see VibrationEffect#startWaveform()
+ */
+ @TestApi
+ public static final class WaveformBuilder {
+ private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+
+ WaveformBuilder() {}
/**
- * @hide
+ * Vibrate with given amplitude for the given duration, in millis, keeping the previous
+ * frequency the same.
+ *
+ * <p>If the duration is zero the vibrator will jump to new amplitude.
+ *
+ * @param amplitude The amplitude for this step
+ * @param duration The duration of this step in milliseconds
+ * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
*/
- public static class PrimitiveEffect implements Parcelable {
- public int id;
- public float scale;
- public int delay;
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
+ @IntRange(from = 0) int duration) {
+ return addStep(amplitude, getPreviousFrequency(), duration);
+ }
- PrimitiveEffect(int id, float scale, int delay) {
- this.id = id;
- this.scale = scale;
- this.delay = delay;
+ /**
+ * Vibrate with given amplitude for the given duration, in millis, keeping the previous
+ * vibration frequency the same.
+ *
+ * <p>If the duration is zero the vibrator will jump to new amplitude.
+ *
+ * @param amplitude The amplitude for this step
+ * @param frequency The frequency for this step
+ * @param duration The duration of this step in milliseconds
+ * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
+ @FloatRange(from = -1f, to = 1f) float frequency,
+ @IntRange(from = 0) int duration) {
+ mSegments.add(new StepSegment(amplitude, frequency, duration));
+ return this;
+ }
+
+ /**
+ * Ramp vibration linearly for the given duration, in millis, from previous amplitude value
+ * to the given one, keeping previous frequency.
+ *
+ * <p>If the duration is zero the vibrator will jump to new amplitude.
+ *
+ * @param amplitude The final amplitude this ramp should reach
+ * @param duration The duration of this ramp in milliseconds
+ * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
+ @IntRange(from = 0) int duration) {
+ return addRamp(amplitude, getPreviousFrequency(), duration);
+ }
+
+ /**
+ * Ramp vibration linearly for the given duration, in millis, from previous amplitude and
+ * frequency values to the given ones.
+ *
+ * <p>If the duration is zero the vibrator will jump to new amplitude and frequency.
+ *
+ * @param amplitude The final amplitude this ramp should reach
+ * @param frequency The final frequency this ramp should reach
+ * @param duration The duration of this ramp in milliseconds
+ * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
+ @FloatRange(from = -1f, to = 1f) float frequency,
+ @IntRange(from = 0) int duration) {
+ mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude, getPreviousFrequency(),
+ frequency, duration));
+ return this;
+ }
+
+ /**
+ * Compose all of the steps together into a single {@link VibrationEffect}.
+ *
+ * The {@link WaveformBuilder} object is still valid after this call, so you can
+ * continue adding more primitives to it and generating more {@link VibrationEffect}s by
+ * calling this method again.
+ *
+ * @return The {@link VibrationEffect} resulting from the composition of the steps.
+ */
+ @NonNull
+ public VibrationEffect build() {
+ return build(/* repeat= */ -1);
+ }
+
+ /**
+ * Compose all of the steps together into a single {@link VibrationEffect}.
+ *
+ * <p>To cause the pattern to repeat, pass the index at which to start the repetition
+ * (starting at 0), or -1 to disable repeating.
+ *
+ * <p>The {@link WaveformBuilder} object is still valid after this call, so you can
+ * continue adding more primitives to it and generating more {@link VibrationEffect}s by
+ * calling this method again.
+ *
+ * @return The {@link VibrationEffect} resulting from the composition of the steps.
+ */
+ @NonNull
+ public VibrationEffect build(int repeat) {
+ if (mSegments.isEmpty()) {
+ throw new IllegalStateException(
+ "WaveformBuilder must have at least one element to build.");
}
+ VibrationEffect effect = new Composed(mSegments, repeat);
+ effect.validate();
+ return effect;
+ }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(id);
- dest.writeFloat(scale);
- dest.writeInt(delay);
+ private float getPreviousFrequency() {
+ if (!mSegments.isEmpty()) {
+ VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
+ if (segment instanceof StepSegment) {
+ return ((StepSegment) segment).getFrequency();
+ } else if (segment instanceof RampSegment) {
+ return ((RampSegment) segment).getEndFrequency();
+ }
}
+ return 0;
+ }
- @Override
- public int describeContents() {
- return 0;
+ private float getPreviousAmplitude() {
+ if (!mSegments.isEmpty()) {
+ VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
+ if (segment instanceof StepSegment) {
+ return ((StepSegment) segment).getAmplitude();
+ } else if (segment instanceof RampSegment) {
+ return ((RampSegment) segment).getEndAmplitude();
+ }
}
-
- @Override
- public String toString() {
- return "PrimitiveEffect{"
- + "id=" + primitiveToString(id)
- + ", scale=" + scale
- + ", delay=" + delay
- + '}';
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- PrimitiveEffect that = (PrimitiveEffect) o;
- return id == that.id
- && Float.compare(that.scale, scale) == 0
- && delay == that.delay;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, scale, delay);
- }
-
-
- public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR =
- new Parcelable.Creator<PrimitiveEffect>() {
- @Override
- public PrimitiveEffect createFromParcel(Parcel in) {
- return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt());
- }
- @Override
- public PrimitiveEffect[] newArray(int size) {
- return new PrimitiveEffect[size];
- }
- };
+ return 0;
}
}
- public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<VibrationEffect> CREATOR =
new Parcelable.Creator<VibrationEffect>() {
@Override
public VibrationEffect createFromParcel(Parcel in) {
- int token = in.readInt();
- if (token == PARCEL_TOKEN_ONE_SHOT) {
- return new OneShot(in);
- } else if (token == PARCEL_TOKEN_WAVEFORM) {
- return new Waveform(in);
- } else if (token == PARCEL_TOKEN_EFFECT) {
- return new Prebaked(in);
- } else if (token == PARCEL_TOKEN_COMPOSITION) {
- return new Composed(in);
- } else {
- throw new IllegalStateException(
- "Unexpected vibration event type token in parcel.");
- }
+ return new Composed(in);
}
@Override
public VibrationEffect[] newArray(int size) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b90d438..a0f70c8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -467,7 +467,7 @@
*/
@NonNull
public boolean[] arePrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
return new boolean[primitiveIds.length];
}
@@ -478,7 +478,7 @@
* @return Whether primitives effects are supported.
*/
public final boolean areAllPrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
for (boolean supported : arePrimitivesSupported(primitiveIds)) {
if (!supported) {
return false;
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 3121b95..64e51e7 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -16,9 +16,13 @@
package android.os;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.IVibrator;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Range;
import android.util.SparseBooleanArray;
import java.util.ArrayList;
@@ -42,27 +46,27 @@
private final SparseBooleanArray mSupportedEffects;
@Nullable
private final SparseBooleanArray mSupportedPrimitives;
- private final float mResonantFrequency;
private final float mQFactor;
+ private final FrequencyMapping mFrequencyMapping;
VibratorInfo(Parcel in) {
mId = in.readInt();
mCapabilities = in.readLong();
mSupportedEffects = in.readSparseBooleanArray();
mSupportedPrimitives = in.readSparseBooleanArray();
- mResonantFrequency = in.readFloat();
mQFactor = in.readFloat();
+ mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
}
/** @hide */
public VibratorInfo(int id, long capabilities, int[] supportedEffects,
- int[] supportedPrimitives, float resonantFrequency, float qFactor) {
+ int[] supportedPrimitives, float qFactor, @NonNull FrequencyMapping frequencyMapping) {
mId = id;
mCapabilities = capabilities;
mSupportedEffects = toSparseBooleanArray(supportedEffects);
mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives);
- mResonantFrequency = resonantFrequency;
mQFactor = qFactor;
+ mFrequencyMapping = frequencyMapping;
}
@Override
@@ -71,8 +75,8 @@
dest.writeLong(mCapabilities);
dest.writeSparseBooleanArray(mSupportedEffects);
dest.writeSparseBooleanArray(mSupportedPrimitives);
- dest.writeFloat(mResonantFrequency);
dest.writeFloat(mQFactor);
+ dest.writeParcelable(mFrequencyMapping, flags);
}
@Override
@@ -92,14 +96,14 @@
return mId == that.mId && mCapabilities == that.mCapabilities
&& Objects.equals(mSupportedEffects, that.mSupportedEffects)
&& Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives)
- && Objects.equals(mResonantFrequency, that.mResonantFrequency)
- && Objects.equals(mQFactor, that.mQFactor);
+ && Objects.equals(mQFactor, that.mQFactor)
+ && Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
}
@Override
public int hashCode() {
return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
- mResonantFrequency, mQFactor);
+ mQFactor, mFrequencyMapping);
}
@Override
@@ -110,8 +114,8 @@
+ ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
+ ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
- + ", mResonantFrequency=" + mResonantFrequency
+ ", mQFactor=" + mQFactor
+ + ", mFrequencyMapping=" + mFrequencyMapping
+ '}';
}
@@ -153,7 +157,8 @@
* @param primitiveId Which primitives to query for.
* @return Whether the primitive is supported.
*/
- public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) {
+ public boolean isPrimitiveSupported(
+ @VibrationEffect.Composition.PrimitiveType int primitiveId) {
return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
&& mSupportedPrimitives.get(primitiveId, false);
}
@@ -176,7 +181,7 @@
* this vibrator is a composite of multiple physical devices.
*/
public float getResonantFrequency() {
- return mResonantFrequency;
+ return mFrequencyMapping.mResonantFrequencyHz;
}
/**
@@ -189,6 +194,52 @@
return mQFactor;
}
+ /**
+ * Return a range of relative frequency values supported by the vibrator.
+ *
+ * @return A range of relative frequency values supported. The range will always contain the
+ * value 0, representing the device resonant frequency. Devices without frequency control will
+ * return the range [0,0]. Devices with frequency control will always return a range containing
+ * the safe range [-1, 1].
+ * @hide
+ */
+ public Range<Float> getFrequencyRange() {
+ return mFrequencyMapping.mRelativeFrequencyRange;
+ }
+
+ /**
+ * Return the maximum amplitude the vibrator can play at given relative frequency.
+ *
+ * @return a value in [0,1] representing the maximum amplitude the device can play at given
+ * relative frequency. Devices without frequency control will return 1 for the input zero
+ * (resonant frequency), and 0 to any other input. Devices with frequency control will return
+ * the supported value, for input in {@code #getFrequencyRange()}, and 0 for any other input.
+ * @hide
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getMaxAmplitude(float relativeFrequency) {
+ if (mFrequencyMapping.isEmpty()) {
+ // The vibrator has not provided values for frequency mapping.
+ // Return the expected behavior for devices without frequency control.
+ return Float.compare(relativeFrequency, 0) == 0 ? 1 : 0;
+ }
+ return mFrequencyMapping.getMaxAmplitude(relativeFrequency);
+ }
+
+ /**
+ * Return absolute frequency value for this vibrator, in hertz, that corresponds to given
+ * relative frequency.
+ *
+ * @retur a value in hertz that corresponds to given relative frequency. Input values outside
+ * {@link #getFrequencyRange()} will return {@link Float#NaN}. Devices without frequency control
+ * will return {@link Float#NaN} for any input.
+ * @hide
+ */
+ @FloatRange(from = 0)
+ public float getAbsoluteFrequency(float relativeFrequency) {
+ return mFrequencyMapping.toHertz(relativeFrequency);
+ }
+
private String[] getCapabilitiesNames() {
List<String> names = new ArrayList<>();
if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
@@ -249,6 +300,209 @@
return array;
}
+ /**
+ * Describes how frequency should be mapped to absolute values for a specific {@link Vibrator}.
+ *
+ * <p>This mapping is defined by the following parameters:
+ *
+ * <ol>
+ * <li>{@code minFrequency}, {@code resonantFrequency} and {@code frequencyResolution}, in
+ * hertz, provided by the vibrator.
+ * <li>{@code maxAmplitudes} a list of values in [0,1] provided by the vibrator, where
+ * {@code maxAmplitudes[i]} represents max supported amplitude at frequency
+ * {@code minFrequency + frequencyResolution * i}.
+ * <li>{@code maxFrequency = minFrequency + frequencyResolution * (maxAmplitudes.length-1)}
+ * <li>{@code suggestedSafeRangeHz} is the suggested frequency range in hertz that should be
+ * mapped to relative values -1 and 1, where 0 maps to {@code resonantFrequency}.
+ * </ol>
+ *
+ * <p>The mapping is defined linearly by the following points:
+ *
+ * <ol>
+ * <li>{@code toHertz(relativeMinFrequency} = minFrequency
+ * <li>{@code toHertz(-1) = resonantFrequency - safeRange / 2}
+ * <li>{@code toHertz(0) = resonantFrequency}
+ * <li>{@code toHertz(1) = resonantFrequency + safeRange / 2}
+ * <li>{@code toHertz(relativeMaxFrequency) = maxFrequency}
+ * </ol>
+ *
+ * @hide
+ */
+ public static final class FrequencyMapping implements Parcelable {
+ private final float mMinFrequencyHz;
+ private final float mResonantFrequencyHz;
+ private final float mFrequencyResolutionHz;
+ private final float mSuggestedSafeRangeHz;
+ private final float[] mMaxAmplitudes;
+
+ // Relative fields calculated from input values:
+ private final Range<Float> mRelativeFrequencyRange;
+
+ FrequencyMapping(Parcel in) {
+ this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(),
+ in.createFloatArray());
+ }
+
+ /** @hide */
+ public FrequencyMapping(float minFrequencyHz, float resonantFrequencyHz,
+ float frequencyResolutionHz, float suggestedSafeRangeHz, float[] maxAmplitudes) {
+ mMinFrequencyHz = minFrequencyHz;
+ mResonantFrequencyHz = resonantFrequencyHz;
+ mFrequencyResolutionHz = frequencyResolutionHz;
+ mSuggestedSafeRangeHz = suggestedSafeRangeHz;
+ mMaxAmplitudes = new float[maxAmplitudes == null ? 0 : maxAmplitudes.length];
+ if (maxAmplitudes != null) {
+ System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
+ }
+
+ float maxFrequencyHz =
+ minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1);
+ if (Float.isNaN(resonantFrequencyHz) || Float.isNaN(minFrequencyHz)
+ || Float.isNaN(frequencyResolutionHz) || Float.isNaN(suggestedSafeRangeHz)
+ || resonantFrequencyHz < minFrequencyHz
+ || resonantFrequencyHz > maxFrequencyHz) {
+ // Some required fields are undefined or have bad values.
+ // Leave this mapping empty.
+ mRelativeFrequencyRange = Range.create(0f, 0f);
+ return;
+ }
+
+ // Calculate actual safe range, limiting the suggested one by the device supported range
+ float safeDelta = MathUtils.min(
+ suggestedSafeRangeHz / 2,
+ resonantFrequencyHz - minFrequencyHz,
+ maxFrequencyHz - resonantFrequencyHz);
+ mRelativeFrequencyRange = Range.create(
+ (minFrequencyHz - resonantFrequencyHz) / safeDelta,
+ (maxFrequencyHz - resonantFrequencyHz) / safeDelta);
+ }
+
+ /**
+ * Returns true if this frequency mapping is empty, i.e. the only supported relative
+ * frequency is 0 (resonant frequency).
+ */
+ public boolean isEmpty() {
+ return Float.compare(mRelativeFrequencyRange.getLower(),
+ mRelativeFrequencyRange.getUpper()) == 0;
+ }
+
+ /**
+ * Returns the frequency value in hertz that is mapped to the given relative frequency.
+ *
+ * @return The mapped frequency, in hertz, or {@link Float#NaN} is value outside the device
+ * supported range.
+ */
+ public float toHertz(float relativeFrequency) {
+ if (!mRelativeFrequencyRange.contains(relativeFrequency)) {
+ return Float.NaN;
+ }
+ float relativeMinFrequency = mRelativeFrequencyRange.getLower();
+ if (Float.compare(relativeMinFrequency, 0) == 0) {
+ // relative supported range is [0,0], so toHertz(0) should be the resonant frequency
+ return mResonantFrequencyHz;
+ }
+ float shift = (mMinFrequencyHz - mResonantFrequencyHz) / relativeMinFrequency;
+ return mResonantFrequencyHz + relativeFrequency * shift;
+ }
+
+ /**
+ * Returns the maximum amplitude the vibrator can reach while playing at given relative
+ * frequency.
+ *
+ * @return A value in [0,1] representing the max amplitude supported at given relative
+ * frequency. This will return 0 if frequency is outside supported range, or if max
+ * amplitude mapping is empty.
+ */
+ public float getMaxAmplitude(float relativeFrequency) {
+ float frequencyHz = toHertz(relativeFrequency);
+ if (Float.isNaN(frequencyHz)) {
+ // Unsupported frequency requested, vibrator cannot play at this frequency.
+ return 0;
+ }
+ float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz;
+ int floorIndex = (int) Math.floor(position);
+ int ceilIndex = (int) Math.ceil(position);
+ if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) {
+ if (mMaxAmplitudes.length > 0) {
+ // This should never happen if the setup of relative frequencies was correct.
+ Log.w(TAG, "Max amplitudes has " + mMaxAmplitudes.length
+ + " entries and was expected to cover the frequency " + frequencyHz
+ + " Hz when starting at min frequency of " + mMinFrequencyHz
+ + " Hz with resolution of " + mFrequencyResolutionHz + " Hz.");
+ }
+ return 0;
+ }
+ if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) {
+ // Value in between two mapped frequency values, use the lowest supported one.
+ return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]);
+ }
+ return mMaxAmplitudes[floorIndex];
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mMinFrequencyHz);
+ dest.writeFloat(mResonantFrequencyHz);
+ dest.writeFloat(mFrequencyResolutionHz);
+ dest.writeFloat(mSuggestedSafeRangeHz);
+ dest.writeFloatArray(mMaxAmplitudes);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FrequencyMapping)) {
+ return false;
+ }
+ FrequencyMapping that = (FrequencyMapping) o;
+ return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
+ && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
+ && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
+ && Float.compare(mSuggestedSafeRangeHz, that.mSuggestedSafeRangeHz) == 0
+ && Arrays.equals(mMaxAmplitudes, that.mMaxAmplitudes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz, mFrequencyResolutionHz,
+ mSuggestedSafeRangeHz, mMaxAmplitudes);
+ }
+
+ @Override
+ public String toString() {
+ return "FrequencyMapping{"
+ + "mMinFrequency=" + mMinFrequencyHz
+ + ", mResonantFrequency=" + mResonantFrequencyHz
+ + ", mMaxFrequency="
+ + (mMinFrequencyHz + mFrequencyResolutionHz * (mMaxAmplitudes.length - 1))
+ + ", mFrequencyResolution=" + mFrequencyResolutionHz
+ + ", mSuggestedSafeRange=" + mSuggestedSafeRangeHz
+ + ", mMaxAmplitudes count=" + mMaxAmplitudes.length
+ + '}';
+ }
+
+ @NonNull
+ public static final Creator<FrequencyMapping> CREATOR =
+ new Creator<FrequencyMapping>() {
+ @Override
+ public FrequencyMapping createFromParcel(Parcel in) {
+ return new FrequencyMapping(in);
+ }
+
+ @Override
+ public FrequencyMapping[] newArray(int size) {
+ return new FrequencyMapping[size];
+ }
+ };
+ }
+
@NonNull
public static final Creator<VibratorInfo> CREATOR =
new Creator<VibratorInfo>() {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 98b4e0b..81c38f8 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -201,4 +201,5 @@
void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91;
void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92;
PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93;
+ boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 94;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index c967deb..cae20ed 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2803,6 +2803,30 @@
}
}
+ /**
+ * Check if {@code uid} with {@code tid} is blocked on IO for {@code reason}.
+ *
+ * This requires {@link ExternalStorageService} the
+ * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+ *
+ * @param volumeUuid the UUID of the storage volume to check IO blocked status
+ * @param uid the UID of the app to check IO blocked status
+ * @param tid the tid of the app to check IO blocked status
+ * @param reason the reason to check IO blocked status for
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid,
+ @AppIoBlockedReason int reason) {
+ Objects.requireNonNull(volumeUuid);
+ try {
+ return mStorageManager.isAppIoBlocked(convert(volumeUuid), uid, tid, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final Object mFuseAppLoopLock = new Object();
@GuardedBy("mFuseAppLoopLock")
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index b5abe2a..36177c4 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,6 +16,8 @@
package android.os.storage;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -289,9 +291,14 @@
return mMaxFileSize;
}
- /** {@hide} */
+ /**
+ * Returns the user that owns this volume
+ *
+ * {@hide}
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- public UserHandle getOwner() {
+ @SystemApi(client = MODULE_LIBRARIES)
+ public @NonNull UserHandle getOwner() {
return mOwner;
}
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
new file mode 100644
index 0000000..78b4346
--- /dev/null
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a prebaked vibration effect.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrebakedSegment extends VibrationEffectSegment {
+ private final int mEffectId;
+ private final boolean mFallback;
+ private final int mEffectStrength;
+
+ PrebakedSegment(@NonNull Parcel in) {
+ mEffectId = in.readInt();
+ mFallback = in.readByte() != 0;
+ mEffectStrength = in.readInt();
+ }
+
+ /** @hide */
+ public PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength) {
+ mEffectId = effectId;
+ mFallback = shouldFallback;
+ mEffectStrength = effectStrength;
+ }
+
+ public int getEffectId() {
+ return mEffectId;
+ }
+
+ public int getEffectStrength() {
+ return mEffectStrength;
+ }
+
+ /** Return true if a fallback effect should be played if this effect is not supported. */
+ public boolean shouldFallback() {
+ return mFallback;
+ }
+
+ @Override
+ public long getDuration() {
+ return -1;
+ }
+
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public PrebakedSegment resolve(int defaultAmplitude) {
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PrebakedSegment scale(float scaleFactor) {
+ // Prebaked effect strength cannot be scaled with this method.
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PrebakedSegment applyEffectStrength(int effectStrength) {
+ if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
+ return new PrebakedSegment(mEffectId, mFallback, effectStrength);
+ }
+ return this;
+ }
+
+ private static boolean isValidEffectStrength(int strength) {
+ switch (strength) {
+ case VibrationEffect.EFFECT_STRENGTH_LIGHT:
+ case VibrationEffect.EFFECT_STRENGTH_MEDIUM:
+ case VibrationEffect.EFFECT_STRENGTH_STRONG:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void validate() {
+ switch (mEffectId) {
+ case VibrationEffect.EFFECT_CLICK:
+ case VibrationEffect.EFFECT_DOUBLE_CLICK:
+ case VibrationEffect.EFFECT_TICK:
+ case VibrationEffect.EFFECT_TEXTURE_TICK:
+ case VibrationEffect.EFFECT_THUD:
+ case VibrationEffect.EFFECT_POP:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ break;
+ default:
+ int[] ringtones = VibrationEffect.RINGTONES;
+ if (mEffectId < ringtones[0] || mEffectId > ringtones[ringtones.length - 1]) {
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect type (value=" + mEffectId + ")");
+ }
+ }
+ if (!isValidEffectStrength(mEffectStrength)) {
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (!(o instanceof PrebakedSegment)) {
+ return false;
+ }
+ PrebakedSegment other = (PrebakedSegment) o;
+ return mEffectId == other.mEffectId
+ && mFallback == other.mFallback
+ && mEffectStrength == other.mEffectStrength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEffectId, mFallback, mEffectStrength);
+ }
+
+ @Override
+ public String toString() {
+ return "Prebaked{effect=" + VibrationEffect.effectIdToString(mEffectId)
+ + ", strength=" + VibrationEffect.effectStrengthToString(mEffectStrength)
+ + ", fallback=" + mFallback
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_PREBAKED);
+ out.writeInt(mEffectId);
+ out.writeByte((byte) (mFallback ? 1 : 0));
+ out.writeInt(mEffectStrength);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<PrebakedSegment> CREATOR =
+ new Parcelable.Creator<PrebakedSegment>() {
+ @Override
+ public PrebakedSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new PrebakedSegment(in);
+ }
+
+ @Override
+ public PrebakedSegment[] newArray(int size) {
+ return new PrebakedSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
new file mode 100644
index 0000000..2ef29cb
--- /dev/null
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a primitive vibration effect after a
+ * specified delay and applying a given scale.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrimitiveSegment extends VibrationEffectSegment {
+ private final int mPrimitiveId;
+ private final float mScale;
+ private final int mDelay;
+
+ PrimitiveSegment(@NonNull Parcel in) {
+ this(in.readInt(), in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public PrimitiveSegment(int id, float scale, int delay) {
+ mPrimitiveId = id;
+ mScale = scale;
+ mDelay = delay;
+ }
+
+ public int getPrimitiveId() {
+ return mPrimitiveId;
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ public int getDelay() {
+ return mDelay;
+ }
+
+ @Override
+ public long getDuration() {
+ return -1;
+ }
+
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0.
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public PrimitiveSegment resolve(int defaultAmplitude) {
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PrimitiveSegment scale(float scaleFactor) {
+ return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor),
+ mDelay);
+ }
+
+ @NonNull
+ @Override
+ public PrimitiveSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public void validate() {
+ Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP,
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId");
+ Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale");
+ Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0");
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(PARCEL_TOKEN_PRIMITIVE);
+ dest.writeInt(mPrimitiveId);
+ dest.writeFloat(mScale);
+ dest.writeInt(mDelay);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Primitive{"
+ + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId)
+ + ", scale=" + mScale
+ + ", delay=" + mDelay
+ + '}';
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrimitiveSegment that = (PrimitiveSegment) o;
+ return mPrimitiveId == that.mPrimitiveId
+ && Float.compare(that.mScale, mScale) == 0
+ && mDelay == that.mDelay;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPrimitiveId, mScale, mDelay);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<PrimitiveSegment> CREATOR =
+ new Parcelable.Creator<PrimitiveSegment>() {
+ @Override
+ public PrimitiveSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new PrimitiveSegment(in);
+ }
+
+ @Override
+ public PrimitiveSegment[] newArray(int size) {
+ return new PrimitiveSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
new file mode 100644
index 0000000..aad87c5
--- /dev/null
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency
+ * for a specified duration.
+ *
+ * @hide
+ */
+@TestApi
+public final class RampSegment extends VibrationEffectSegment {
+ private final float mStartAmplitude;
+ private final float mStartFrequency;
+ private final float mEndAmplitude;
+ private final float mEndFrequency;
+ private final int mDuration;
+
+ RampSegment(@NonNull Parcel in) {
+ this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public RampSegment(float startAmplitude, float endAmplitude, float startFrequency,
+ float endFrequency, int duration) {
+ mStartAmplitude = startAmplitude;
+ mEndAmplitude = endAmplitude;
+ mStartFrequency = startFrequency;
+ mEndFrequency = endFrequency;
+ mDuration = duration;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof RampSegment)) {
+ return false;
+ }
+ RampSegment other = (RampSegment) o;
+ return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0
+ && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0
+ && Float.compare(mStartFrequency, other.mStartFrequency) == 0
+ && Float.compare(mEndFrequency, other.mEndFrequency) == 0
+ && mDuration == other.mDuration;
+ }
+
+ public float getStartAmplitude() {
+ return mStartAmplitude;
+ }
+
+ public float getEndAmplitude() {
+ return mEndAmplitude;
+ }
+
+ public float getStartFrequency() {
+ return mStartFrequency;
+ }
+
+ public float getEndFrequency() {
+ return mEndFrequency;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ return mStartAmplitude > 0 || mEndAmplitude > 0;
+ }
+
+ @Override
+ public void validate() {
+ Preconditions.checkArgumentNonnegative(mDuration,
+ "Durations must all be >= 0, got " + mDuration);
+ Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude");
+ Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
+ }
+
+
+ @NonNull
+ @Override
+ public RampSegment resolve(int defaultAmplitude) {
+ // Default amplitude is not supported for ramping.
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public RampSegment scale(float scaleFactor) {
+ float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor);
+ float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor);
+ if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
+ && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
+ return this;
+ }
+ return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequency, mEndFrequency,
+ mDuration);
+ }
+
+ @NonNull
+ @Override
+ public RampSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequency, mEndFrequency,
+ mDuration);
+ }
+
+ @Override
+ public String toString() {
+ return "Ramp{startAmplitude=" + mStartAmplitude
+ + ", endAmplitude=" + mEndAmplitude
+ + ", startFrequency=" + mStartFrequency
+ + ", endFrequency=" + mEndFrequency
+ + ", duration=" + mDuration
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_RAMP);
+ out.writeFloat(mStartAmplitude);
+ out.writeFloat(mEndAmplitude);
+ out.writeFloat(mStartFrequency);
+ out.writeFloat(mEndFrequency);
+ out.writeInt(mDuration);
+ }
+
+ @NonNull
+ public static final Creator<RampSegment> CREATOR =
+ new Creator<RampSegment>() {
+ @Override
+ public RampSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new RampSegment(in);
+ }
+
+ @Override
+ public RampSegment[] newArray(int size) {
+ return new RampSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
new file mode 100644
index 0000000..11209e0
--- /dev/null
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude and
+ * frequency for a specified duration.
+ *
+ * @hide
+ */
+@TestApi
+public final class StepSegment extends VibrationEffectSegment {
+ private final float mAmplitude;
+ private final float mFrequency;
+ private final int mDuration;
+
+ StepSegment(@NonNull Parcel in) {
+ this(in.readFloat(), in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public StepSegment(float amplitude, float frequency, int duration) {
+ mAmplitude = amplitude;
+ mFrequency = frequency;
+ mDuration = duration;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof StepSegment)) {
+ return false;
+ }
+ StepSegment other = (StepSegment) o;
+ return Float.compare(mAmplitude, other.mAmplitude) == 0
+ && Float.compare(mFrequency, other.mFrequency) == 0
+ && mDuration == other.mDuration;
+ }
+
+ public float getAmplitude() {
+ return mAmplitude;
+ }
+
+ public float getFrequency() {
+ return mFrequency;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later.
+ return Float.compare(mAmplitude, 0) != 0;
+ }
+
+ @Override
+ public void validate() {
+ Preconditions.checkArgumentNonnegative(mDuration,
+ "Durations must all be >= 0, got " + mDuration);
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+ Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude");
+ }
+ }
+
+ @NonNull
+ @Override
+ public StepSegment resolve(int defaultAmplitude) {
+ if (defaultAmplitude > VibrationEffect.MAX_AMPLITUDE || defaultAmplitude <= 0) {
+ throw new IllegalArgumentException(
+ "amplitude must be between 1 and 255 inclusive (amplitude="
+ + defaultAmplitude + ")");
+ }
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+ return this;
+ }
+ return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mFrequency,
+ mDuration);
+ }
+
+ @NonNull
+ @Override
+ public StepSegment scale(float scaleFactor) {
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+ return this;
+ }
+ return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequency,
+ mDuration);
+ }
+
+ @NonNull
+ @Override
+ public StepSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAmplitude, mFrequency, mDuration);
+ }
+
+ @Override
+ public String toString() {
+ return "Step{amplitude=" + mAmplitude
+ + ", frequency=" + mFrequency
+ + ", duration=" + mDuration
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_STEP);
+ out.writeFloat(mAmplitude);
+ out.writeFloat(mFrequency);
+ out.writeInt(mDuration);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<StepSegment> CREATOR =
+ new Parcelable.Creator<StepSegment>() {
+ @Override
+ public StepSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new StepSegment(in);
+ }
+
+ @Override
+ public StepSegment[] newArray(int size) {
+ return new StepSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
new file mode 100644
index 0000000..5b42845
--- /dev/null
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+/**
+ * Representation of a single segment of a {@link VibrationEffect}.
+ *
+ * <p>Vibration effects are represented as a sequence of segments that describes how vibration
+ * amplitude and frequency changes over time. Segments can be described as one of the following:
+ *
+ * <ol>
+ * <li>A predefined vibration effect;
+ * <li>A composable effect primitive;
+ * <li>Fixed amplitude and frequency values to be held for a specified duration;
+ * <li>Pairs of amplitude and frequency values to be ramped to for a specified duration;
+ * </ol>
+ *
+ * @hide
+ */
+@TestApi
+@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
+public abstract class VibrationEffectSegment implements Parcelable {
+ static final int PARCEL_TOKEN_PREBAKED = 1;
+ static final int PARCEL_TOKEN_PRIMITIVE = 2;
+ static final int PARCEL_TOKEN_STEP = 3;
+ static final int PARCEL_TOKEN_RAMP = 4;
+
+ /** Prevent subclassing from outside of this package */
+ VibrationEffectSegment() {
+ }
+
+ /**
+ * Gets the estimated duration of the segment in milliseconds.
+ *
+ * <p>For segments with an unknown duration (e.g. prebaked or primitive effects where the length
+ * is device and potentially run-time dependent), this returns -1.
+ */
+ public abstract long getDuration();
+
+ /** Returns true if this segment plays at a non-zero amplitude at some point. */
+ public abstract boolean hasNonZeroAmplitude();
+
+ /** Validates the segment, throwing exceptions if any parameter is invalid. */
+ public abstract void validate();
+
+ /**
+ * Resolves amplitudes set to {@link VibrationEffect#DEFAULT_AMPLITUDE}.
+ *
+ * <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger
+ * than {@link VibrationEffect#MAX_AMPLITUDE}.
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude);
+
+ /**
+ * Scale the segment intensity with the given factor.
+ *
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
+
+ /**
+ * Applies given effect strength to prebaked effects.
+ *
+ * @param effectStrength new effect strength to be applied, one of
+ * VibrationEffect.EFFECT_STRENGTH_*.
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
+
+ @NonNull
+ public static final Creator<VibrationEffectSegment> CREATOR =
+ new Creator<VibrationEffectSegment>() {
+ @Override
+ public VibrationEffectSegment createFromParcel(Parcel in) {
+ switch (in.readInt()) {
+ case PARCEL_TOKEN_STEP:
+ return new StepSegment(in);
+ case PARCEL_TOKEN_RAMP:
+ return new RampSegment(in);
+ case PARCEL_TOKEN_PREBAKED:
+ return new PrebakedSegment(in);
+ case PARCEL_TOKEN_PRIMITIVE:
+ return new PrimitiveSegment(in);
+ default:
+ throw new IllegalStateException(
+ "Unexpected vibration event type token in parcel.");
+ }
+ }
+
+ @Override
+ public VibrationEffectSegment[] newArray(int size) {
+ return new VibrationEffectSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 80a3e16..921911b 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -283,9 +283,7 @@
continue;
}
- if (packageName.equals(SYSTEM_PKG)
- || (!shouldShowPermissionsHub()
- && !isUserSensitive(packageName, user, op))) {
+ if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) {
continue;
}
@@ -372,8 +370,10 @@
proxyLabels.put(usage, new ArrayList<>());
proxyUids.add(usage.uid);
}
- if (!mostRecentUsages.containsKey(usage.uid) || usage.lastAccessTime
- > mostRecentUsages.get(usage.uid).lastAccessTime) {
+ // If this usage is not by the system, and is more recent than the next-most recent
+ // for it's uid, save it.
+ if (!usage.packageName.equals(SYSTEM_PKG) && (!mostRecentUsages.containsKey(usage.uid)
+ || usage.lastAccessTime > mostRecentUsages.get(usage.uid).lastAccessTime)) {
mostRecentUsages.put(usage.uid, usage);
}
}
@@ -416,20 +416,22 @@
}
proxyUids.add(currentUsage.uid);
- try {
- PackageManager userPkgManager =
- getUserContext(currentUsage.getUser()).getPackageManager();
- ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
- currentUsage.packageName, 0);
- CharSequence appLabel = appInfo.loadLabel(userPkgManager);
- // If we don't already have the app label, and it's not the same as the main
- // app, add it
- if (!proxyLabelList.contains(appLabel)
- && !currentUsage.packageName.equals(start.packageName)) {
- proxyLabelList.add(appLabel);
+ // Don't add an app label for the main app, or the system app
+ if (!currentUsage.packageName.equals(start.packageName)
+ && !currentUsage.packageName.equals(SYSTEM_PKG)) {
+ try {
+ PackageManager userPkgManager =
+ getUserContext(currentUsage.getUser()).getPackageManager();
+ ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
+ currentUsage.packageName, 0);
+ CharSequence appLabel = appInfo.loadLabel(userPkgManager);
+ // If we don't already have the app label add it
+ if (!proxyLabelList.contains(appLabel)) {
+ proxyLabelList.add(appLabel);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
}
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore
}
iterNum++;
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7e40497..31cf63c 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -108,6 +108,13 @@
public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
/**
+ * Namespace for all AppSearch related features.
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_APPSEARCH = "appsearch";
+
+ /**
* Namespace for app standby configurations.
*
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 71ffa92..719c383 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1068,8 +1068,8 @@
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS =
- "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS";
+ public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS =
+ "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
/**
* Activity Action: Show screen for controlling which apps can draw on top of other apps.
@@ -8523,6 +8523,15 @@
"one_handed_tutorial_show_count";
/**
+ * Indicates whether transform is enabled.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRANSFORM_ENABLED = "transform_enabled";
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
@@ -13112,7 +13121,7 @@
* @see #ENABLE_RESTRICTED_BUCKET
* @hide
*/
- public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 0;
+ public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 1;
/**
* Whether or not app auto restriction is enabled. When it is enabled, settings app will
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 374de9c..38945f5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4559,6 +4559,15 @@
public static final String VOICE_REG_STATE = "voice_reg_state";
/**
+ * An integer value indicating the current data service state.
+ * <p>
+ * Valid values: {@link ServiceState#STATE_IN_SERVICE},
+ * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
+ * {@link ServiceState#STATE_POWER_OFF}.
+ */
+ public static final String DATA_REG_STATE = "data_reg_state";
+
+ /**
* The current registered operator numeric id.
* <p>
* In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
@@ -4574,6 +4583,24 @@
* This is the same as {@link ServiceState#getIsManualSelection()}.
*/
public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
+
+ /**
+ * The current data network type.
+ * <p>
+ * This is the same as {@link TelephonyManager#getDataNetworkType()}.
+ */
+ public static final String DATA_NETWORK_TYPE = "data_network_type";
+
+ /**
+ * An integer value indicating the current duplex mode if the radio technology is LTE,
+ * LTE-CA or NR.
+ * <p>
+ * Valid values: {@link ServiceState#DUPLEX_MODE_UNKNOWN},
+ * {@link ServiceState#DUPLEX_MODE_FDD}, {@link ServiceState#DUPLEX_MODE_TDD}.
+ * <p>
+ * This is the same as {@link ServiceState#getDuplexMode()}.
+ */
+ public static final String DUPLEX_MODE = "duplex_mode";
}
/**
@@ -5317,5 +5344,14 @@
* @hide
*/
public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status";
+
+ /**
+ * TelephonyProvider column name for information selected contacts that allow device to
+ * device sharing.
+ *
+ * @hide
+ */
+ public static final String COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS =
+ "d2d_sharing_contacts";
}
}
diff --git a/core/java/android/service/translation/TranslationServiceInfo.java b/core/java/android/service/translation/TranslationServiceInfo.java
index 18cc29d..c7017b2 100644
--- a/core/java/android/service/translation/TranslationServiceInfo.java
+++ b/core/java/android/service/translation/TranslationServiceInfo.java
@@ -100,9 +100,9 @@
if (!Manifest.permission.BIND_TRANSLATION_SERVICE.equals(si.permission)) {
Slog.w(TAG, "TranslationServiceInfo from '" + si.packageName
+ "' does not require permission "
- + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+ + Manifest.permission.BIND_TRANSLATION_SERVICE);
throw new SecurityException("Service does not require permission "
- + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+ + Manifest.permission.BIND_TRANSLATION_SERVICE);
}
mServiceInfo = si;
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index def13db..1ea40be 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -41,10 +41,10 @@
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.Slog;
@@ -239,18 +239,6 @@
public @interface ModelParams {}
/**
- * Indicates that the given audio data is a false alert for {@link VoiceInteractionService}.
- */
- public static final int HOTWORD_DETECTION_FALSE_ALERT = 0;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = { "HOTWORD_DETECTION_" }, value = {
- HOTWORD_DETECTION_FALSE_ALERT,
- })
- public @interface HotwordDetectionResult {}
-
- /**
* Controls the sensitivity threshold adjustment factor for a given model.
* Negative value corresponds to less sensitive model (high threshold) and
* a positive value corresponds to a more sensitive model (low threshold).
@@ -478,11 +466,14 @@
public abstract void onRecognitionResumed();
/**
- * Called when the validated result is invalid.
+ * Called when the {@link HotwordDetectionService second stage detection} did not detect the
+ * keyphrase.
*
- * @param reason The reason why the validated result is invalid.
+ * @param result Info about the second stage detection result, provided by the
+ * {@link HotwordDetectionService}.
*/
- public void onRejected(@HotwordDetectionResult int reason) {}
+ public void onRejected(@Nullable HotwordRejectedResult result) {
+ }
}
/**
@@ -494,8 +485,8 @@
* @param supportHotwordDetectionService {@code true} if hotword detection service should be
* triggered, otherwise {@code false}.
* @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. The system strips out any remotable objects or other
- * contents that can be used to communicate with other processes.
+ * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * other contents that can be used to communicate with other processes.
* @param sharedMemory The unrestricted data blob provided by the
* {@link VoiceInteractionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
@@ -505,7 +496,7 @@
public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
IVoiceInteractionManagerService modelManagementService, int targetSdkVersion,
- boolean supportHotwordDetectionService, @Nullable Bundle options,
+ boolean supportHotwordDetectionService, @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
mText = text;
mLocale = locale;
@@ -534,8 +525,8 @@
* Set configuration and pass read-only data to hotword detection service.
*
* @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. The system strips out any remotable objects or other
- * contents that can be used to communicate with other processes.
+ * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * other contents that can be used to communicate with other processes.
* @param sharedMemory The unrestricted data blob provided by the
* {@link VoiceInteractionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
@@ -544,7 +535,7 @@
*
* @hide
*/
- public final void setHotwordDetectionServiceConfig(@Nullable Bundle options,
+ public final void setHotwordDetectionServiceConfig(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
if (DBG) {
Slog.d(TAG, "setHotwordDetectionServiceConfig()");
@@ -1069,13 +1060,13 @@
}
@Override
- public void onRejected(int reason) {
+ public void onRejected(HotwordRejectedResult result) {
if (DBG) {
- Slog.d(TAG, "onRejected(" + reason + ")");
+ Slog.d(TAG, "onRejected(" + result + ")");
} else {
Slog.i(TAG, "onRejected");
}
- Message.obtain(mHandler, MSG_HOTWORD_REJECTED, reason).sendToTarget();
+ Message.obtain(mHandler, MSG_HOTWORD_REJECTED, result).sendToTarget();
}
@Override
@@ -1124,7 +1115,7 @@
mExternalCallback.onRecognitionResumed();
break;
case MSG_HOTWORD_REJECTED:
- mExternalCallback.onRejected(msg.arg1);
+ mExternalCallback.onRejected((HotwordRejectedResult) msg.obj);
break;
default:
super.handleMessage(msg);
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index fcef26f..686268c 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -27,11 +27,11 @@
import android.app.Service;
import android.content.Intent;
import android.media.AudioFormat;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.Log;
@@ -82,7 +82,8 @@
}
@Override
- public void setConfig(Bundle options, SharedMemory sharedMemory) throws RemoteException {
+ public void setConfig(PersistableBundle options, SharedMemory sharedMemory)
+ throws RemoteException {
if (DBG) {
Log.d(TAG, "#setConfig");
}
@@ -137,13 +138,13 @@
/**
* Called when the {@link VoiceInteractionService#createAlwaysOnHotwordDetector(String, Locale,
- * Bundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or {@link AlwaysOnHotwordDetector#
- * setHotwordDetectionServiceConfig(Bundle, SharedMemory)} requests an update of the hotword
- * detection parameters.
+ * PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or
+ * {@link AlwaysOnHotwordDetector#setHotwordDetectionServiceConfig(PersistableBundle,
+ * SharedMemory)} requests an update of the hotword detection parameters.
*
* @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. The system strips out any remotable objects or other
- * contents that can be used to communicate with other processes.
+ * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * other contents that can be used to communicate with other processes.
* @param sharedMemory The unrestricted data blob provided by the
* {@link VoiceInteractionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
@@ -151,7 +152,8 @@
* @hide
*/
@SystemApi
- public void onUpdateState(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) {
+ public void onUpdateState(@Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory) {
}
/**
@@ -180,11 +182,14 @@
}
/**
- * Called when the detected result is invalid.
+ * Informs the {@link AlwaysOnHotwordDetector} that the keyphrase was not detected.
+ *
+ * @param result Info about the second stage detection result. This is provided to
+ * the {@link AlwaysOnHotwordDetector}.
*/
- public void onRejected() {
+ public void onRejected(@Nullable HotwordRejectedResult result) {
try {
- mRemoteCallback.onRejected();
+ mRemoteCallback.onRejected(result);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
index bb95bb8..6f641e1 100644
--- a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
+++ b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl
@@ -16,6 +16,8 @@
package android.service.voice;
+import android.service.voice.HotwordRejectedResult;
+
/**
* Callback for returning the detected result from the HotwordDetectionService.
*
@@ -28,7 +30,7 @@
void onDetected();
/**
- * Called when the detected result is invalid.
+ * Sends {@code result} to the HotwordDetector.
*/
- void onRejected();
+ void onRejected(in HotwordRejectedResult result);
}
diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/IHotwordDetectionService.aidl
index 8f0874a..8d01dd1 100644
--- a/core/java/android/service/voice/IHotwordDetectionService.aidl
+++ b/core/java/android/service/voice/IHotwordDetectionService.aidl
@@ -17,8 +17,8 @@
package android.service.voice;
import android.media.AudioFormat;
-import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.service.voice.IDspHotwordDetectionCallback;
@@ -34,5 +34,5 @@
long timeoutMillis,
in IDspHotwordDetectionCallback callback);
- void setConfig(in Bundle options, in SharedMemory sharedMemory);
+ void setConfig(in PersistableBundle options, in SharedMemory sharedMemory);
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 9ba39a1..cb3791d 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -33,6 +33,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
@@ -342,8 +343,8 @@
* @param keyphrase The keyphrase that's being used, for example "Hello Android".
* @param locale The locale for which the enrollment needs to be performed.
* @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. The system strips out any remotable objects or other
- * contents that can be used to communicate with other processes.
+ * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * other contents that can be used to communicate with other processes.
* @param sharedMemory The unrestricted data blob provided by the
* {@link VoiceInteractionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
@@ -358,7 +359,7 @@
public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
@SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly
@SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
- @Nullable Bundle options,
+ @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
@@ -370,7 +371,7 @@
@SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly
@SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
boolean supportHotwordDetectionService,
- @Nullable Bundle options,
+ @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
if (mSystemService == null) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 919c6e5..913ceae 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -71,7 +71,7 @@
DEFAULT_FLAGS.put("settings_silky_home", "true");
DEFAULT_FLAGS.put("settings_contextual_home", "false");
DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
- DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "false");
+ DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "false");
}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index e5106e2..2a43222 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -55,6 +55,12 @@
/**
* Logs a {@link Log.VERBOSE} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void v(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.VERBOSE)) return;
@@ -75,6 +81,12 @@
/**
* Logs a {@link Log.DEBUG} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void d(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.DEBUG)) return;
@@ -94,6 +106,12 @@
/**
* Logs a {@link Log.INFO} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void i(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.INFO)) return;
@@ -118,6 +136,12 @@
/**
* Logs a {@link Log.WARN} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void w(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -127,6 +151,12 @@
/**
* Logs a {@link Log.WARN} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -147,6 +177,12 @@
/**
* Logs a {@link Log.ERROR} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void e(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.ERROR)) return;
@@ -156,6 +192,12 @@
/**
* Logs a {@link Log.ERROR} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.ERROR)) return;
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index b0916d3..f2bc0c5 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -164,7 +164,7 @@
}
/**
- * Returns the number of key-value mappings that this SparseIntArray
+ * Returns the number of key-value mappings that this SparseLongArray
* currently stores.
*/
public int size() {
@@ -246,7 +246,7 @@
}
/**
- * Removes all key-value mappings from this SparseIntArray.
+ * Removes all key-value mappings from this SparseLongArray.
*/
public void clear() {
mSize = 0;
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index b28cfb8..2fcaec9 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -130,6 +130,16 @@
public abstract void triggerManagerServiceDump(String where);
/**
+ * Being called while taking a bugreport so that tracing files can be included in the bugreport
+ * when the IME tracing is running. Does nothing otherwise.
+ *
+ * @param pw Print writer
+ */
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ // does nothing by default.
+ }
+
+ /**
* Sets whether ime tracing is enabled.
*
* @param enabled Tells whether ime tracing should be enabled or disabled.
@@ -153,11 +163,6 @@
}
/**
- * Writes the current tracing data to the specific output proto file.
- */
- public abstract void writeTracesToFiles();
-
- /**
* Starts a new IME trace if one is not already started.
*
* @param pw Print writer
@@ -171,14 +176,6 @@
*/
public abstract void stopTrace(@Nullable PrintWriter pw);
- /**
- * Stops the IME trace if one was previously started.
- *
- * @param pw Print writer
- * @param writeToFile If the current buffer should be written to disk or not
- */
- public abstract void stopTrace(@Nullable PrintWriter pw, boolean writeToFile);
-
private static boolean isSystemProcess() {
return ActivityThread.isSystem();
}
diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java
index 35a81b7..17cdc46 100644
--- a/core/java/android/util/imetracing/ImeTracingClientImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java
@@ -99,18 +99,10 @@
}
@Override
- public void writeTracesToFiles() {
- }
-
- @Override
public void startTrace(PrintWriter pw) {
}
@Override
public void stopTrace(PrintWriter pw) {
}
-
- @Override
- public void stopTrace(PrintWriter pw, boolean writeToFile) {
- }
}
diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java
index 77f017a..06e4c50 100644
--- a/core/java/android/util/imetracing/ImeTracingServerImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java
@@ -139,14 +139,6 @@
}
}
- @GuardedBy("mEnabledLock")
- @Override
- public void writeTracesToFiles() {
- synchronized (mEnabledLock) {
- writeTracesToFilesLocked();
- }
- }
-
private void writeTracesToFilesLocked() {
try {
ProtoOutputStream clientsProto = new ProtoOutputStream();
@@ -192,12 +184,6 @@
@Override
public void stopTrace(@Nullable PrintWriter pw) {
- stopTrace(pw, true /* writeToFile */);
- }
-
- @GuardedBy("mEnabledLock")
- @Override
- public void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) {
if (IS_USER) {
Log.w(TAG, "Warn: Tracing is not supported on user builds.");
return;
@@ -213,9 +199,35 @@
+ TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
+ TRACE_FILENAME_IMMS);
sEnabled = false;
- if (writeToFile) {
- writeTracesToFilesLocked();
+ writeTracesToFilesLocked();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ if (IS_USER) {
+ return;
+ }
+ synchronized (mEnabledLock) {
+ if (!isAvailable() || !isEnabled()) {
+ return;
}
+ // Temporarily stop accepting logs from trace event providers. There is a small chance
+ // that we may drop some trace events while writing the file, but we currently need to
+ // live with that. Note that addToBuffer() also has a bug that it doesn't do
+ // read-acquire so flipping sEnabled here doesn't even guarantee that addToBuffer() will
+ // temporarily stop accepting incoming events...
+ // TODO(b/175761228): Implement atomic snapshot to avoid downtime.
+ // TODO(b/175761228): Fix synchronization around sEnabled.
+ sEnabled = false;
+ logAndPrintln(pw, "Writing traces in " + TRACE_DIRNAME + ": "
+ + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
+ + TRACE_FILENAME_IMMS);
+ writeTracesToFilesLocked();
+ sEnabled = true;
}
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 870fd8c..11b161a 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -79,6 +79,26 @@
mInputToken = inputToken;
}
+ /**
+ * Constructs a copy of {@code SurfacePackage} with an independent lifetime.
+ *
+ * The caller can use this to create an independent copy in situations where ownership of
+ * the {@code SurfacePackage} would be transferred elsewhere, such as attaching to a
+ * {@code SurfaceView}, returning as {@code Binder} result value, etc. The caller is
+ * responsible for releasing this copy when its done.
+ *
+ * @param other {@code SurfacePackage} to create a copy of.
+ */
+ public SurfacePackage(@NonNull SurfacePackage other) {
+ SurfaceControl otherSurfaceControl = other.mSurfaceControl;
+ if (otherSurfaceControl != null && otherSurfaceControl.isValid()) {
+ mSurfaceControl = new SurfaceControl();
+ mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage");
+ }
+ mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
+ mInputToken = other.mInputToken;
+ }
+
private SurfacePackage(Parcel in) {
mSurfaceControl = new SurfaceControl();
mSurfaceControl.readFromParcel(in);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3cd3902..6edd071 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2017,10 +2017,12 @@
}
mServedInputConnectionWrapper = servedContext;
- try {
- if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ if (DEBUG) {
+ Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags));
+ }
+ try {
final Completable.InputBindResult value = Completable.createInputBindResult();
mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
@@ -2028,37 +2030,37 @@
view.getContext().getApplicationInfo().targetSdkVersion,
ResultCallbacks.of(value));
res = Completable.getResult(value);
- if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
- if (res == null) {
- Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
- + " null. startInputReason="
- + InputMethodDebug.startInputReasonToString(startInputReason)
- + " editorInfo=" + tba
- + " startInputFlags="
- + InputMethodDebug.startInputFlagsToString(startInputFlags));
- return false;
- }
- mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix();
- mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
- if (res.id != null) {
- setInputChannelLocked(res.channel);
- mBindSequence = res.sequence;
- mCurMethod = res.method; // for @UnsupportedAppUsage
- mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method);
- mCurId = res.id;
- } else if (res.channel != null && res.channel != mCurChannel) {
- res.channel.dispose();
- }
- switch (res.result) {
- case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
- mRestartOnNextWindowFocus = true;
- break;
- }
- if (mCurrentInputMethodSession != null && mCompletions != null) {
- mCurrentInputMethodSession.displayCompletions(mCompletions);
- }
} catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
+ throw e.rethrowFromSystemServer();
+ }
+ if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+ if (res == null) {
+ Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
+ + " null. startInputReason="
+ + InputMethodDebug.startInputReasonToString(startInputReason)
+ + " editorInfo=" + tba
+ + " startInputFlags="
+ + InputMethodDebug.startInputFlagsToString(startInputFlags));
+ return false;
+ }
+ mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix();
+ mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
+ if (res.id != null) {
+ setInputChannelLocked(res.channel);
+ mBindSequence = res.sequence;
+ mCurMethod = res.method; // for @UnsupportedAppUsage
+ mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method);
+ mCurId = res.id;
+ } else if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
+ switch (res.result) {
+ case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+ mRestartOnNextWindowFocus = true;
+ break;
+ }
+ if (mCurrentInputMethodSession != null && mCompletions != null) {
+ mCurrentInputMethodSession.displayCompletions(mCompletions);
}
}
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index dbc32e9..9c53f46 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -46,4 +46,5 @@
void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
+ void getServiceSettingsActivity(in IResultReceiver result, int userId);
}
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 66b45f3..dfa7095 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -35,6 +35,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.SyncResultReceiver;
import java.util.ArrayList;
import java.util.Collections;
@@ -263,6 +264,31 @@
//TODO: Add method to propagate updates to mTCapabilityUpdateListeners
+ /**
+ * Returns an immutable PendingIntent which can used by apps to launch translation settings.
+ *
+ * @return An immutable PendingIntent or {@code null} if one of reason met:
+ * <ul>
+ * <li>Device manufacturer (OEM) does not provide TranslationService.</li>
+ * <li>The TranslationService doesn't provide the Settings.</li>
+ * </ul>
+ **/
+ @Nullable
+ public PendingIntent getTranslationSettingsActivityIntent() {
+ final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+ try {
+ mService.getServiceSettingsActivity(resultReceiver, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ try {
+ return resultReceiver.getParcelableResult();
+ } catch (SyncResultReceiver.TimeoutException e) {
+ Log.e(TAG, "Fail to get translation service settings activity.");
+ return null;
+ }
+ }
+
void removeTranslator(int id) {
synchronized (mLock) {
mTranslators.remove(id);
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 34ad659..1951194 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -130,11 +130,11 @@
public @interface EdgeEffectType {
}
- private static final float LINEAR_STRETCH_INTENSITY = 0.03f;
+ private static final float LINEAR_STRETCH_INTENSITY = 0.06f;
- private static final float EXP_STRETCH_INTENSITY = 0.02f;
+ private static final float EXP_STRETCH_INTENSITY = 0.06f;
- private static final float SCROLL_DIST_AFFECTED_BY_EXP_STRETCH = 0.4f;
+ private static final float SCROLL_DIST_AFFECTED_BY_EXP_STRETCH = 0.33f;
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "EdgeEffect";
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 616a0d0..9789d70 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -211,14 +211,14 @@
view.mParceledIconBitmap = mParceledIconBitmap;
}
// branding image
- if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) {
+ if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0 && mBrandingDrawable != null) {
final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
params.width = mBrandingImageWidth;
params.height = mBrandingImageHeight;
view.mBrandingImageView.setLayoutParams(params);
- }
- if (mBrandingDrawable != null) {
view.mBrandingImageView.setBackground(mBrandingDrawable);
+ } else {
+ view.mBrandingImageView.setVisibility(GONE);
}
if (mParceledBrandingBitmap != null) {
view.mParceledBrandingBitmap = mParceledBrandingBitmap;
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index 65bd841..23314e7 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.hardware.soundtrigger.SoundTrigger;
+import android.service.voice.HotwordRejectedResult;
/**
* @hide
@@ -41,11 +42,13 @@
void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent);
/**
- * Called when the validated result is invalid.
+ * Called when the {@link HotwordDetectionService second stage detection} did not detect the
+ * keyphrase.
*
- * @param reason The reason why the validated result is invalid.
+ * @param result Info about the second stage detection result, provided by the
+ * {@link HotwordDetectionService}.
*/
- void onRejected(int reason);
+ void onRejected(in HotwordRejectedResult result);
/**
* Called when the detection fails due to an error.
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 592f7c7..2a022e6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.media.permission.Identity;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.SharedMemory;
@@ -229,13 +230,14 @@
* Set configuration and pass read-only data to hotword detection service.
*
* @param options Application configuration data provided by the
- * {@link VoiceInteractionService}. The system strips out any remotable objects or other
- * contents that can be used to communicate with other processes.
+ * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
+ * other contents that can be used to communicate with other processes.
* @param sharedMemory The unrestricted data blob provided by the
* {@link VoiceInteractionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
*/
- void setHotwordDetectionServiceConfig(in Bundle options, in SharedMemory sharedMemory);
+ void setHotwordDetectionServiceConfig(
+ in PersistableBundle options, in SharedMemory sharedMemory);
/**
* Requests to shutdown hotword detection service.
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index a5b894d..c7a36ee 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -374,11 +374,6 @@
*/
public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow";
- /**
- * (boolean) Whether scrolling screenshots are enabled.
- */
- public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
-
// Flags related to Nav Bar
/**
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a043756..ec0a8d8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1195,6 +1195,7 @@
public BatteryStatsImpl(Clocks clocks) {
init(clocks);
+ mStartClockTimeMs = System.currentTimeMillis();
mStatsFile = null;
mCheckinFile = null;
mDailyFile = null;
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 6dd612e..4f99c94 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -134,7 +134,7 @@
final BatteryUsageStats.Builder batteryUsageStatsBuilder =
new BatteryUsageStats.Builder(customPowerComponentCount, customTimeComponentCount)
- .setStatsStartRealtime(mStats.getStatsStartRealtime() / 1000);
+ .setStatsStartTimestamp(mStats.getStartClockTime());
SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats();
for (int i = uidStats.size() - 1; i >= 0; i--) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f49a834..f1fa5db 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -238,6 +238,7 @@
"android.hardware.camera.device@3.2",
"media_permission-aidl-cpp",
"libandroidicu",
+ "libandroid_net",
"libbpf_android",
"libnetdbpf",
"libnetdutils",
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 52d21a8..10927b9 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -18,28 +18,28 @@
//#define LOG_NDEBUG 0
-#include <nativehelper/JNIHelp.h>
-
#include <android_runtime/AndroidRuntime.h>
-#include <log/log.h>
-#include <utils/Looper.h>
#include <input/InputTransport.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Looper.h>
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
#include "android_view_KeyEvent.h"
#include "android_view_MotionEvent.h"
+#include "core_jni_helpers.h"
-#include <nativehelper/ScopedLocalRef.h>
+#include <inttypes.h>
#include <unordered_map>
-#include "core_jni_helpers.h"
using android::base::Result;
namespace android {
// Log debug messages about the dispatch cycle.
-static const bool kDebugDispatchCycle = false;
+static constexpr bool kDebugDispatchCycle = false;
static struct {
jclass clazz;
@@ -74,8 +74,10 @@
return mInputPublisher.getChannel()->getName();
}
- virtual int handleEvent(int receiveFd, int events, void* data);
+ int handleEvent(int receiveFd, int events, void* data) override;
status_t receiveFinishedSignals(JNIEnv* env);
+ bool notifyFinishedSignal(JNIEnv* env, jobject sender, const InputPublisher::Finished& finished,
+ bool skipCallbacks);
};
NativeInputEventSender::NativeInputEventSender(JNIEnv* env, jobject senderWeak,
@@ -196,8 +198,13 @@
ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str());
}
- ScopedLocalRef<jobject> senderObj(env, NULL);
- bool skipCallbacks = false;
+ ScopedLocalRef<jobject> senderObj(env, jniGetReferent(env, mSenderWeakGlobal));
+ if (!senderObj.get()) {
+ ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
+ getInputChannelName().c_str());
+ return DEAD_OBJECT;
+ }
+ bool skipCallbacks = false; // stop calling Java functions after an exception occurs
for (;;) {
Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal();
if (!result.ok()) {
@@ -206,45 +213,55 @@
return OK;
}
ALOGE("channel '%s' ~ Failed to consume finished signals. status=%d",
- getInputChannelName().c_str(), status);
+ getInputChannelName().c_str(), status);
return status;
}
- auto it = mPublishedSeqMap.find(result->seq);
- if (it == mPublishedSeqMap.end()) {
- continue;
- }
-
- uint32_t seq = it->second;
- mPublishedSeqMap.erase(it);
-
- if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
- getInputChannelName().c_str(), seq, result->handled ? "true" : "false",
- mPublishedSeqMap.size());
- }
-
- if (!skipCallbacks) {
- if (!senderObj.get()) {
- senderObj.reset(jniGetReferent(env, mSenderWeakGlobal));
- if (!senderObj.get()) {
- ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
- getInputChannelName().c_str());
- return DEAD_OBJECT;
- }
- }
-
- env->CallVoidMethod(senderObj.get(),
- gInputEventSenderClassInfo.dispatchInputEventFinished,
- static_cast<jint>(seq), static_cast<jboolean>(result->handled));
- if (env->ExceptionCheck()) {
- ALOGE("Exception dispatching finished signal.");
- skipCallbacks = true;
- }
+ const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks);
+ if (!notified) {
+ skipCallbacks = true;
}
}
}
+/**
+ * Invoke the Java function dispatchInputEventFinished for the received "Finished" signal.
+ * Set the variable 'skipCallbacks' to 'true' if a Java exception occurred.
+ * Java function will only be called if 'skipCallbacks' is originally 'false'.
+ *
+ * Return "false" if an exception occurred while calling the Java function
+ * "true" otherwise
+ */
+bool NativeInputEventSender::notifyFinishedSignal(JNIEnv* env, jobject sender,
+ const InputPublisher::Finished& finished,
+ bool skipCallbacks) {
+ auto it = mPublishedSeqMap.find(finished.seq);
+ if (it == mPublishedSeqMap.end()) {
+ ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq);
+ // Since this is coming from the receiver (typically app), it's possible that an app
+ // does something wrong and sends bad data. Just ignore and process other events.
+ return true;
+ }
+ const uint32_t seq = it->second;
+ mPublishedSeqMap.erase(it);
+
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
+ getInputChannelName().c_str(), seq, finished.handled ? "true" : "false",
+ mPublishedSeqMap.size());
+ }
+ if (skipCallbacks) {
+ return true;
+ }
+
+ env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished,
+ static_cast<jint>(seq), static_cast<jboolean>(finished.handled));
+ if (env->ExceptionCheck()) {
+ ALOGE("Exception dispatching finished signal for seq=%" PRIu32, seq);
+ return false;
+ }
+ return true;
+}
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject senderWeak,
jobject inputChannelObj, jobject messageQueueObj) {
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index bbb0edd..4f3ae28 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -178,4 +178,6 @@
CM_DETECT_INTERACTION = 13;
CM_INVALIDATION_REQUESTER = 14;
CM_INVALIDATE = 15;
+ CM_STOP_USER = 16;
+ CM_START_USER = 17;
}
\ No newline at end of file
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index aab054f..7b97524d 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -21,40 +21,49 @@
import "frameworks/base/core/proto/android/privacy.proto";
-message OneShotProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 duration = 1;
- repeated int32 amplitude = 2;
-}
-
-message WaveformProto {
+message StepSegmentProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 timings = 1;
- repeated int32 amplitudes = 2;
- required bool repeat = 3;
+ optional int32 duration = 1;
+ optional float amplitude = 2;
+ optional float frequency = 3;
}
-message PrebakedProto {
+message RampSegmentProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 duration = 1;
+ optional float startAmplitude = 2;
+ optional float endAmplitude = 3;
+ optional float startFrequency = 4;
+ optional float endFrequency = 5;
+}
+
+message PrebakedSegmentProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 effect_id = 1;
optional int32 effect_strength = 2;
optional int32 fallback = 3;
}
-message ComposedProto {
+message PrimitiveSegmentProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 effect_ids = 1;
- repeated float effect_scales = 2;
- repeated int32 delays = 3;
+ optional int32 primitive_id = 1;
+ optional float scale = 2;
+ optional int32 delay = 3;
+}
+
+message SegmentProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional PrebakedSegmentProto prebaked = 1;
+ optional PrimitiveSegmentProto primitive = 2;
+ optional StepSegmentProto step = 3;
+ optional RampSegmentProto ramp = 4;
}
// A com.android.os.VibrationEffect object.
message VibrationEffectProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional OneShotProto oneshot = 1;
- optional WaveformProto waveform = 2;
- optional PrebakedProto prebaked = 3;
- optional ComposedProto composed = 4;
+ optional SegmentProto segments = 1;
+ required int32 repeat = 2;
}
message SyncVibrationEffectProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8d7f542..8a47135 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1388,6 +1388,14 @@
android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
android:protectionLevel="dangerous|instant" />
+ <!-- Required to be able to discover and connect to nearby Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission-group android:name="android.permission-group.NEARBY_DEVICES"
+ android:icon="@drawable/ic_qs_bluetooth"
+ android:label="@string/permgrouplab_nearby_devices"
+ android:description="@string/permgroupdesc_nearby_devices"
+ android:priority="750" />
+
<!-- @SystemApi @TestApi Required to be able to access the camera device in the background.
This permission is not intended to be held by apps.
<p>Protection level: internal
@@ -1930,6 +1938,22 @@
android:label="@string/permlab_bluetooth"
android:protectionLevel="normal" />
+ <!-- Required to be able to discover and pair nearby Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BLUETOOTH_SCAN"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_bluetooth_scan"
+ android:label="@string/permlab_bluetooth_scan"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to connect to paired Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BLUETOOTH_CONNECT"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_bluetooth_connect"
+ android:label="@string/permlab_bluetooth_connect"
+ android:protectionLevel="dangerous" />
+
<!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
user from using them until they are unsuspended.
@hide
@@ -1963,6 +1987,12 @@
<permission android:name="android.permission.BLUETOOTH_STACK"
android:protectionLevel="signature" />
+ <!-- Allows uhid write access for creating virtual input devices
+ @hide
+ -->
+ <permission android:name="android.permission.VIRTUAL_INPUT_DEVICE"
+ android:protectionLevel="signature" />
+
<!-- Allows applications to perform I/O operations over NFC.
<p>Protection level: normal
-->
@@ -2939,6 +2969,15 @@
<permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
android:protectionLevel="signature" />
+ <!-- Allows system clock time suggestions from an external clock / time source to be made.
+ The nature of "external" could be highly form-factor specific. Example, times
+ obtained via the VHAL for Android Auto OS.
+ <p>Not for use by third-party applications.
+ @SystemApi @hide
+ -->
+ <permission android:name="android.permission.SUGGEST_EXTERNAL_TIME"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows applications like settings to manage configuration associated with automatic time
and time zone detection.
<p>Not for use by third-party applications.
@@ -4911,6 +4950,11 @@
<permission android:name="android.permission.SET_INITIAL_LOCK"
android:protectionLevel="signature|setup"/>
+ <!-- @TestApi Allows applications to set and verify lockscreen credentials.
+ @hide -->
+ <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"
+ android:protectionLevel="signature"/>
+
<!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_FINGERPRINT"
android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/drawable/bottomsheet_background.xml b/core/res/res/drawable/bottomsheet_background.xml
index 3a8ad71..00d53004 100644
--- a/core/res/res/drawable/bottomsheet_background.xml
+++ b/core/res/res/drawable/bottomsheet_background.xml
@@ -18,5 +18,5 @@
<corners
android:topLeftRadius="@dimen/config_bottomDialogCornerRadius"
android:topRightRadius="@dimen/config_bottomDialogCornerRadius"/>
- <solid android:color="?attr/colorBackgroundFloating" />
+ <solid android:color="?attr/colorBackground" />
</shape>
diff --git a/core/res/res/drawable/chooser_action_button_bg.xml b/core/res/res/drawable/chooser_action_button_bg.xml
index 0dd9e9c7..18bbd93 100644
--- a/core/res/res/drawable/chooser_action_button_bg.xml
+++ b/core/res/res/drawable/chooser_action_button_bg.xml
@@ -15,7 +15,7 @@
~ limitations under the License
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/lighter_gray">
+ android:color="?android:attr/colorControlHighlight">
<item>
<inset
android:insetLeft="0dp"
@@ -23,10 +23,8 @@
android:insetRight="0dp"
android:insetBottom="8dp">
<shape android:shape="rectangle">
- <corners android:radius="16dp"></corners>
- <stroke android:width="1dp"
- android:color="?attr/opacityListDivider" />
- <solid android:color="?attr/colorBackgroundFloating" />
+ <corners android:radius="16dp" />
+ <solid android:color="@color/system_neutral2_100" />
</shape>
</inset>
</item>
diff --git a/core/res/res/layout/chooser_action_button.xml b/core/res/res/layout/chooser_action_button.xml
index 6af7937..16ffaa4 100644
--- a/core/res/res/layout/chooser_action_button.xml
+++ b/core/res/res/layout/chooser_action_button.xml
@@ -25,6 +25,7 @@
android:singleLine="true"
android:clickable="true"
android:background="@drawable/chooser_action_button_bg"
- android:drawableTint="@color/chooser_chip_icon"
+ android:drawableTint="?android:textColorPrimary"
android:drawableTintMode="src_in"
+ style="?android:attr/borderlessButtonStyle"
/>
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index c0de693..10683b1 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -67,7 +67,7 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
- android:background="?attr/colorBackgroundFloating">
+ android:background="?attr/colorBackground">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
@@ -84,7 +84,7 @@
android:layout_alwaysShow="true"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical" />
<FrameLayout
android:id="@android:id/tabcontent"
diff --git a/core/res/res/layout/chooser_grid_preview_file.xml b/core/res/res/layout/chooser_grid_preview_file.xml
index 2a39215..d8c1d17 100644
--- a/core/res/res/layout/chooser_grid_preview_file.xml
+++ b/core/res/res/layout/chooser_grid_preview_file.xml
@@ -24,7 +24,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/chooser_view_spacing"
- android:background="?attr/colorBackgroundFloating">
+ android:background="?attr/colorBackground">
<LinearLayout
android:layout_width="@dimen/chooser_preview_width"
diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml
index 62df165..0d04d7f3 100644
--- a/core/res/res/layout/chooser_grid_preview_image.xml
+++ b/core/res/res/layout/chooser_grid_preview_image.xml
@@ -22,14 +22,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating">
+ android:background="?attr/colorBackground">
<RelativeLayout
android:id="@+id/content_preview_image_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingBottom="@dimen/chooser_view_spacing"
- android:background="?attr/colorBackgroundFloating">
+ android:background="?attr/colorBackground">
<view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
android:id="@+id/content_preview_image_1_large"
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 1d18648..bc4f327 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -23,7 +23,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:background="?android:attr/colorBackgroundFloating">
+ android:background="?android:attr/colorBackground">
<RelativeLayout
android:layout_width="@dimen/chooser_preview_width"
diff --git a/core/res/res/layout/chooser_list_per_profile.xml b/core/res/res/layout/chooser_list_per_profile.xml
index 86dc71c..912173c 100644
--- a/core/res/res/layout/chooser_list_per_profile.xml
+++ b/core/res/res/layout/chooser_list_per_profile.xml
@@ -23,7 +23,7 @@
android:layoutManager="com.android.internal.app.ChooserGridLayoutManager"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:scrollbars="none"
android:elevation="1dp"
android:nestedScrollingEnabled="true" />
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index bdcfeb2..8594c33 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -84,7 +84,7 @@
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:text="@string/noApplications"
android:padding="@dimen/chooser_edge_margin_normal"
android:layout_marginBottom="56dp"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 6fde1df..d791598 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -68,7 +68,7 @@
android:layout_alwaysShow="true"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical" />
<FrameLayout
@@ -76,7 +76,7 @@
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/colorBackgroundFloating"/>
+ android:background="?attr/colorBackground"/>
<TabHost
android:id="@+id/profile_tabhost"
@@ -85,7 +85,7 @@
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:accessibilityTraversalAfter="@id/title"
- android:background="?attr/colorBackgroundFloating">
+ android:background="?attr/colorBackground">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
@@ -101,7 +101,7 @@
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical"/>
<FrameLayout
android:id="@android:id/tabcontent"
@@ -120,13 +120,13 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:layout_ignoreOffset="true">
<View
android:id="@+id/resolver_button_bar_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical" />
<LinearLayout
android:id="@+id/button_bar"
diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml
index 9410301..d6ca7ab 100644
--- a/core/res/res/layout/resolver_list_per_profile.xml
+++ b/core/res/res/layout/resolver_list_per_profile.xml
@@ -24,7 +24,7 @@
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
android:scrollbarStyle="outsideOverlay"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 4a5aa02..7610e73 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -148,7 +148,7 @@
android:layout_alwaysShow="true"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical" />
<FrameLayout
@@ -157,7 +157,7 @@
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/colorBackgroundFloating"/>
+ android:background="?attr/colorBackground"/>
<TabHost
android:layout_alwaysShow="true"
@@ -166,7 +166,7 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
- android:background="?attr/colorBackgroundFloating">
+ android:background="?attr/colorBackground">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
@@ -182,7 +182,7 @@
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical"/>
<FrameLayout
android:id="@android:id/tabcontent"
@@ -200,6 +200,6 @@
android:layout_alwaysShow="true"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
+ android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical" />
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 4410e94..baffa5a 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -36,7 +36,6 @@
<color name="resolver_empty_state_text">#FFFFFF</color>
<color name="resolver_empty_state_icon">#FFFFFF</color>
- <color name="chooser_chip_icon">#8AB4F8</color> <!-- Blue 300 -->
<color name="personal_apps_suspension_notification_color">#8AB4F8</color>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5e95f94..4b15e01 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1125,6 +1125,15 @@
to framework controls (via colorControlActivated). -->
<attr name="colorAccent" format="color" />
+ <!-- Light accent color used on Material NEXT buttons. @hide -->
+ <attr name="colorAccentPrimary" format="color" />
+
+ <!-- Secondary accent color used on Material NEXT buttons. @hide -->
+ <attr name="colorAccentSecondary" format="color" />
+
+ <!-- Tertiary accent color used on Material NEXT buttons. @hide -->
+ <attr name="colorAccentTertiary" format="color" />
+
<!-- The color applied to framework controls in their normal state. -->
<attr name="colorControlNormal" format="color" />
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 140163e..38e8f83 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1853,6 +1853,25 @@
-->
<attr name="preserveLegacyExternalStorage" format="boolean" />
+ <!-- If {@code true} this app would like optimized external storage access.
+
+ <p> This flag can only be used by apps holding
+ <ul>
+ <li>{@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission or
+ <li>{@link android.app.role}#SYSTEM_GALLERY role.
+ </ul>
+ When the flag is set, bulk file path operations will be optimized.
+
+ The default value is {@code true} if
+ <ul>
+ <li>app has {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission and
+ targets targetSDK<=30.
+ <li>app has {@link android.app.role}#SYSTEM_GALLERY role and targetSDK<=29
+ </ul>
+ {@code false} otherwise.
+ -->
+ <attr name="requestOptimizedExternalStorageAccess" format="boolean" />
+
<!-- If {@code true} this app declares that it should be visible to all other apps on
device, regardless of what they declare via the {@code queries} tags in their
manifest.
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 91896fe..0213c60 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -237,7 +237,6 @@
<color name="resolver_text_color_secondary_dark">#ffC4C6C6</color>
<color name="resolver_empty_state_text">#FF202124</color>
<color name="resolver_empty_state_icon">#FF5F6368</color>
- <color name="chooser_chip_icon">#FF1A73E8</color> <!-- Blue 600 -->
<!-- Color for personal app suspension notification button text and icon tint. -->
<color name="personal_apps_suspension_notification_color">#1A73E8</color>
@@ -249,34 +248,34 @@
<color name="system_accent1_0">#ffffff</color>
<!-- Shade of the accent system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_50">#91fff4</color>
+ <color name="system_accent1_50">#9CFFF2</color>
<!-- Shade of the accent system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_100">#83f6e5</color>
+ <color name="system_accent1_100">#8DF5E3</color>
<!-- Shade of the accent system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_200">#65d9c9</color>
+ <color name="system_accent1_200">#71D8C7</color>
<!-- Shade of the accent system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_300">#45bdae</color>
+ <color name="system_accent1_300">#53BCAC</color>
<!-- Shade of the accent system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_400">#1fa293</color>
+ <color name="system_accent1_400">#34A192</color>
<!-- Shade of the accent system color at 49% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_500">#008377</color>
+ <color name="system_accent1_500">#008375</color>
<!-- Shade of the accent system color at 40% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_600">#006d61</color>
+ <color name="system_accent1_600">#006C5F</color>
<!-- Shade of the accent system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_700">#005449</color>
+ <color name="system_accent1_700">#005747</color>
<!-- Shade of the accent system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_800">#003c33</color>
+ <color name="system_accent1_800">#003E31</color>
<!-- Shade of the accent system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent1_900">#00271e</color>
+ <color name="system_accent1_900">#002214</color>
<!-- Darkest shade of the accent color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_1000">#000000</color>
@@ -286,34 +285,34 @@
<color name="system_accent2_0">#ffffff</color>
<!-- Shade of the secondary accent system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_50">#91fff4</color>
+ <color name="system_accent2_50">#CDFAF1</color>
<!-- Shade of the secondary accent system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_100">#83f6e5</color>
+ <color name="system_accent2_100">#BFEBE3</color>
<!-- Shade of the secondary accent system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_200">#65d9c9</color>
+ <color name="system_accent2_200">#A4CFC7</color>
<!-- Shade of the secondary accent system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_300">#45bdae</color>
+ <color name="system_accent2_300">#89B4AC</color>
<!-- Shade of the secondary accent system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_400">#1fa293</color>
+ <color name="system_accent2_400">#6F9991</color>
<!-- Shade of the secondary accent system color at 49% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_500">#008377</color>
+ <color name="system_accent2_500">#537C75</color>
<!-- Shade of the secondary accent system color at 40% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_600">#006d61</color>
+ <color name="system_accent2_600">#3D665F</color>
<!-- Shade of the secondary accent system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_700">#005449</color>
+ <color name="system_accent2_700">#254E47</color>
<!-- Shade of the secondary accent system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_800">#003c33</color>
+ <color name="system_accent2_800">#0C3731</color>
<!-- Shade of the secondary accent system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent2_900">#00271e</color>
+ <color name="system_accent2_900">#00211C</color>
<!-- Darkest shade of the secondary accent color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_1000">#000000</color>
@@ -323,34 +322,34 @@
<color name="system_accent3_0">#ffffff</color>
<!-- Shade of the tertiary accent system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_50">#91fff4</color>
+ <color name="system_accent3_50">#F9EAFF</color>
<!-- Shade of the tertiary accent system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_100">#83f6e5</color>
+ <color name="system_accent3_100">#ECDBFF</color>
<!-- Shade of the tertiary accent system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_200">#65d9c9</color>
+ <color name="system_accent3_200">#CFBFEB</color>
<!-- Shade of the tertiary accent system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_300">#45bdae</color>
+ <color name="system_accent3_300">#B3A4CF</color>
<!-- Shade of the tertiary accent system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_400">#1fa293</color>
+ <color name="system_accent3_400">#988AB3</color>
<!-- Shade of the tertiary accent system color at 49% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_500">#008377</color>
+ <color name="system_accent3_500">#7B6E96</color>
<!-- Shade of the tertiary accent system color at 40% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_600">#006d61</color>
+ <color name="system_accent3_600">#64587F</color>
<!-- Shade of the tertiary accent system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_700">#005449</color>
+ <color name="system_accent3_700">#4C4165</color>
<!-- Shade of the tertiary accent system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_800">#003c33</color>
+ <color name="system_accent3_800">#352B4D</color>
<!-- Shade of the tertiary accent system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_accent3_900">#00271e</color>
+ <color name="system_accent3_900">#1E1636</color>
<!-- Darkest shade of the tertiary accent color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_1000">#000000</color>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 3fbd7ca..0b41769 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -36,6 +36,9 @@
<color name="accent_device_default_light">@color/system_accent1_600</color>
<color name="accent_device_default_dark">@color/system_accent1_200</color>
<color name="accent_device_default">@color/accent_device_default_light</color>
+ <color name="accent_primary_device_default">@color/system_accent1_100</color>
+ <color name="accent_secondary_device_default">@color/system_accent2_100</color>
+ <color name="accent_tertiary_device_default">@color/system_accent3_100</color>
<color name="background_device_default_dark">@color/system_neutral1_800</color>
<color name="background_device_default_light">@color/system_neutral1_50</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b4ef63f..8df3221 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -951,6 +951,11 @@
-->
<integer name="config_longPressOnPowerBehavior">1</integer>
+ <!-- Whether the setting to change long press on power behaviour from default to assistant (5)
+ is available in Settings.
+ -->
+ <bool name="config_longPressOnPowerForAssistantSettingAvailable">true</bool>
+
<!-- Control the behavior when the user long presses the power button for a long time.
0 - Nothing
1 - Global actions menu
@@ -4575,6 +4580,11 @@
check after reboot or airplane mode toggling -->
<bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
+ <!-- Boolean indicating whether all CB messages should be disabled on this device. This config
+ is intended to be used by OEMs who need to disable CB messages for regulatory requirements,
+ (e.g. the device is a tablet in a country where tablets should not receive CB messages) -->
+ <bool translatable="false" name="config_disable_all_cb_messages">false</bool>
+
<!-- Screen Wake Keys
Determines whether the specified key groups can be used to wake up the device. -->
<bool name="config_wakeOnDpadKeyPress">true</bool>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7694faf..3259caf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3092,6 +3092,7 @@
<public name="attributionTags"/>
<public name="suppressesSpellChecker" />
<public name="usesPermissionFlags" />
+ <public name="requestOptimizedExternalStorageAccess" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7ea762c..0b3c405 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -822,6 +822,11 @@
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_camera">take pictures and record video</string>
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+ <string name="permgrouplab_nearby_devices">Nearby Bluetooth Devices</string>
+ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+ <string name="permgroupdesc_nearby_devices">discover and connect to nearby Bluetooth devices</string>
+
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_calllog">Call logs</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1471,6 +1476,14 @@
<string name="permdesc_bluetooth" product="default">Allows the app to view the
configuration of the Bluetooth on the phone, and to make and accept
connections with paired devices.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]-->
+ <string name="permlab_bluetooth_scan">discover and pair nearby Bluetooth devices</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]-->
+ <string name="permdesc_bluetooth_scan" product="default">Allows the app to discover and pair nearby Bluetooth devices</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]-->
+ <string name="permlab_bluetooth_connect">connect to paired Bluetooth devices</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]-->
+ <string name="permdesc_bluetooth_connect" product="default">Allows the app to connect to paired Bluetooth devices</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_preferredPaymentInfo">Preferred NFC Payment Service Information</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d6a6f4d..a704936 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -431,6 +431,7 @@
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
<java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+ <java-symbol type="bool" name="config_longPressOnPowerForAssistantSettingAvailable" />
<java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
<java-symbol type="integer" name="config_veryLongPressTimeout" />
<java-symbol type="integer" name="config_longPressOnBackBehavior" />
@@ -3954,6 +3955,7 @@
<java-symbol type="layout" name="chooser_action_button" />
<java-symbol type="dimen" name="chooser_action_button_icon_size" />
<java-symbol type="string" name="config_defaultNearbySharingComponent" />
+ <java-symbol type="bool" name="config_disable_all_cb_messages" />
<java-symbol type="drawable" name="ic_close" />
<java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" />
@@ -4219,6 +4221,10 @@
<java-symbol type="bool" name="config_enableOneHandedKeyguard" />
+ <java-symbol type="attr" name="colorAccentPrimary" />
+ <java-symbol type="attr" name="colorAccentSecondary" />
+ <java-symbol type="attr" name="colorAccentTertiary" />
+
<!-- CEC Configuration -->
<java-symbol type="bool" name="config_cecHdmiCecEnabled_userConfigurable" />
<java-symbol type="bool" name="config_cecHdmiCecControlEnabled_allowed" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 16d720b..e40e31e 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -214,6 +214,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -237,6 +240,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -272,6 +278,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -309,6 +318,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -345,6 +357,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -396,6 +411,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -424,6 +442,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -458,6 +479,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -493,6 +517,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -544,6 +571,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -580,6 +610,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -614,6 +647,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -650,6 +686,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -685,6 +724,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -720,6 +762,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -755,6 +800,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -790,6 +838,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -829,6 +880,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -865,6 +919,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -898,6 +955,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -1085,6 +1145,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1104,6 +1167,9 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1138,6 +1204,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1173,6 +1242,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1210,6 +1282,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1246,6 +1321,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1299,6 +1377,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1326,6 +1407,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1363,6 +1447,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1401,6 +1488,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1440,6 +1530,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
<item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
@@ -1459,6 +1552,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
<item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
@@ -1477,6 +1573,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1516,6 +1615,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1553,6 +1655,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1589,6 +1694,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1624,6 +1732,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1659,6 +1770,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1692,6 +1806,9 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1742,6 +1859,9 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorEdgeEffect">@color/edge_effect_device_default_light</item>
@@ -1772,6 +1892,9 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorControlNormal">?attr/textColorPrimary</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
@@ -1797,6 +1920,9 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
@@ -1815,6 +1941,9 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
@@ -1849,6 +1978,9 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1893,6 +2025,9 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -1930,6 +2065,9 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
<item name="colorError">@color/error_color_device_default_light</item>
<item name="colorBackground">@color/background_device_default_light</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
@@ -2030,10 +2168,16 @@
<style name="ThemeOverlay.DeviceDefault.Accent">
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
</style>
<style name="ThemeOverlay.DeviceDefault.Accent.Light">
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
</style>
<!-- Theme overlay that replaces colorAccent with the colorAccent from {@link #Theme_DeviceDefault_DayNight}. -->
@@ -2042,6 +2186,9 @@
<style name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" parent="ThemeOverlay.Material.Dark.ActionBar">
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
</style>
<style name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" parent="Theme.DeviceDefault.NoActionBar.Fullscreen">
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index 6c28607..6a01abe 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="com.android.app.gamemanagertests"
android:sharedUserId="android.uid.system" >
- <application>
+ <application android:appCategory="game">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index 0c96411..baecc8c 100644
--- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -37,9 +37,6 @@
@SmallTest
@Presubmit
public final class GameManagerTests {
- private static final String PACKAGE_NAME_0 = "com.android.app0";
- private static final String PACKAGE_NAME_1 = "com.android.app1";
-
protected Context mContext;
private GameManager mGameManager;
private String mPackageName;
@@ -52,8 +49,6 @@
// Reset the Game Mode for the test app, since it persists across invocations.
mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED);
- mGameManager.setGameMode(PACKAGE_NAME_0, GameManager.GAME_MODE_UNSUPPORTED);
- mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_UNSUPPORTED);
}
@Test
@@ -73,14 +68,14 @@
@Test
public void testPrivilegedGameModeGetterSetter() {
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- mGameManager.getGameMode(PACKAGE_NAME_0));
+ mGameManager.getGameMode(mPackageName));
- mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD);
+ mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
assertEquals(GameManager.GAME_MODE_STANDARD,
- mGameManager.getGameMode(PACKAGE_NAME_1));
+ mGameManager.getGameMode(mPackageName));
- mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE);
+ mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE);
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
- mGameManager.getGameMode(PACKAGE_NAME_1));
+ mGameManager.getGameMode(mPackageName));
}
}
diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml
index 6849a90..f8c69ac 100644
--- a/core/tests/bluetoothtests/AndroidManifest.xml
+++ b/core/tests/bluetoothtests/AndroidManifest.xml
@@ -20,6 +20,8 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index f31233b..408624a 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -44,6 +44,8 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
index 7cb6804..36da927 100644
--- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -235,6 +235,7 @@
.setStatuses(statusList).setNotificationKey("key")
.setNotificationContent("content")
.setNotificationDataUri(Uri.parse("data"))
+ .setMessagesCount(2)
.setIntent(new Intent())
.build();
@@ -256,6 +257,7 @@
assertThat(readTile.getNotificationKey()).isEqualTo(tile.getNotificationKey());
assertThat(readTile.getNotificationContent()).isEqualTo(tile.getNotificationContent());
assertThat(readTile.getNotificationDataUri()).isEqualTo(tile.getNotificationDataUri());
+ assertThat(readTile.getMessagesCount()).isEqualTo(tile.getMessagesCount());
assertThat(readTile.getIntent().toString()).isEqualTo(tile.getIntent().toString());
}
@@ -291,6 +293,17 @@
}
@Test
+ public void testMessagesCount() {
+ PeopleSpaceTile tile =
+ new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build(),
+ mLauncherApps)
+ .setMessagesCount(2)
+ .build();
+
+ assertThat(tile.getMessagesCount()).isEqualTo(2);
+ }
+
+ @Test
public void testIntent() {
PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 11239db..30b2d8e 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -28,13 +28,15 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.Arrays;
@Presubmit
@RunWith(JUnit4.class)
public class CombinedVibrationEffectTest {
private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
- private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
+ private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.Composed(
+ new ArrayList<>(), 0);
@Test
public void testValidateMono() {
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index d555cd9..009665f 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -18,13 +18,9 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-import static org.junit.Assert.assertArrayEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -35,11 +31,13 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
import android.platform.test.annotations.Presubmit;
import com.android.internal.R;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@@ -53,9 +51,6 @@
private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3";
private static final String UNKNOWN_URI = "content://test/system/other_audio";
- private static final float INTENSITY_SCALE_TOLERANCE = 1e-2f;
- private static final int AMPLITUDE_SCALE_TOLERANCE = 1;
-
private static final long TEST_TIMING = 100;
private static final int TEST_AMPLITUDE = 100;
private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
@@ -68,12 +63,6 @@
VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
private static final VibrationEffect TEST_WAVEFORM =
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
- private static final VibrationEffect TEST_COMPOSED =
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0f, 100)
- .compose();
@Test
public void getRingtones_noPrebakedRingtones() {
@@ -129,7 +118,16 @@
public void testValidateWaveform() {
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate();
+ VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 1, /* duration= */ 10)
+ .addRamp(/* amplitude= */ 0, /* duration= */ 20)
+ .addStep(/* amplitude= */ 1, /* frequency*/ 1, /* duration= */ 100)
+ .addRamp(/* amplitude= */ 0.5f, /* frequency*/ -1, /* duration= */ 50)
+ .build()
+ .validate();
+ assertThrows(IllegalStateException.class,
+ () -> VibrationEffect.startWaveform().build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.createWaveform(new long[0], new int[0], -1).validate());
assertThrows(IllegalArgumentException.class,
@@ -143,17 +141,31 @@
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.createWaveform(
TEST_TIMINGS, TEST_AMPLITUDES, TEST_TIMINGS.length).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ -2, 10).build().validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveform()
+ .addStep(1, /* duration= */ -1).build().validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveform()
+ .addStep(1, 0, /* duration= */ -1).build().validate());
}
@Test
public void testValidateComposed() {
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addEffect(TEST_ONE_SHOT)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addEffect(TEST_WAVEFORM, 100)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+ .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.compose()
.validate();
+ assertThrows(IllegalStateException.class,
+ () -> VibrationEffect.startComposition().compose().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startComposition().addPrimitive(-1).compose().validate());
assertThrows(IllegalArgumentException.class,
@@ -163,256 +175,138 @@
.validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, -10)
+ .compose()
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startComposition()
+ .addEffect(TEST_ONE_SHOT, /* delay= */ -10)
+ .compose()
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1)
.compose()
.validate());
}
@Test
- public void testScalePrebaked_scalesFallbackEffect() {
- VibrationEffect.Prebaked prebaked =
- (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]);
- assertSame(prebaked, prebaked.scale(0.5f));
-
- prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM, TEST_ONE_SHOT);
- VibrationEffect.OneShot scaledFallback =
- (VibrationEffect.OneShot) prebaked.scale(0.5f).getFallbackEffect();
- assertEquals(34, scaledFallback.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
- }
-
- @Test
- public void testResolvePrebaked_resolvesFallbackEffectIfSet() {
- VibrationEffect.Prebaked prebaked =
- (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]);
- assertSame(prebaked, prebaked.resolve(1000));
-
- prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM,
- VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE));
- VibrationEffect.OneShot resolvedFallback =
- (VibrationEffect.OneShot) prebaked.resolve(10).getFallbackEffect();
- assertEquals(10, resolvedFallback.getAmplitude());
- }
-
- @Test
- public void testScaleOneShot() {
- VibrationEffect.OneShot unset = new VibrationEffect.OneShot(
- TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
- assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, unset.scale(2).getAmplitude());
-
- VibrationEffect.OneShot initial = (VibrationEffect.OneShot) TEST_ONE_SHOT;
-
- VibrationEffect.OneShot halved = initial.scale(0.5f);
- assertEquals(34, halved.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
- VibrationEffect.OneShot copied = initial.scale(1f);
- assertEquals(TEST_AMPLITUDE, copied.getAmplitude());
-
- VibrationEffect.OneShot scaledUp = initial.scale(1.5f);
- assertTrue(scaledUp.getAmplitude() > initial.getAmplitude());
- VibrationEffect.OneShot restored = scaledUp.scale(2 / 3f);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(105, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
- VibrationEffect.OneShot scaledDown = initial.scale(0.8f);
- assertTrue(scaledDown.getAmplitude() < initial.getAmplitude());
- restored = scaledDown.scale(1.25f);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(101, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
- // Does not go below min amplitude while scaling down.
- VibrationEffect.OneShot minAmplitude = new VibrationEffect.OneShot(TEST_TIMING, 1);
- assertEquals(1, minAmplitude.scale(0.5f).getAmplitude());
- }
-
- @Test
public void testResolveOneShot() {
- VibrationEffect.OneShot initial = (VibrationEffect.OneShot) DEFAULT_ONE_SHOT;
- VibrationEffect.OneShot resolved = initial.resolve(239);
- assertNotSame(initial, resolved);
- assertEquals(239, resolved.getAmplitude());
+ VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
+ assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
- // Ignores input when amplitude already set.
- VibrationEffect.OneShot resolved2 = resolved.resolve(10);
- assertSame(resolved, resolved2);
- assertEquals(239, resolved2.getAmplitude());
- }
-
- @Test
- public void testResolveOneshotFailsWhenMaxAmplitudeAboveThreshold() {
- try {
- TEST_ONE_SHOT.resolve(1000);
- fail("Max amplitude above threshold, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testResolveOneshotFailsWhenAmplitudeNonPositive() {
- try {
- TEST_ONE_SHOT.resolve(0);
- fail("Amplitude is set to zero, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testScaleWaveform() {
- VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM;
-
- VibrationEffect.Waveform copied = initial.scale(1f);
- assertArrayEquals(TEST_AMPLITUDES, copied.getAmplitudes());
-
- VibrationEffect.Waveform scaled = initial.scale(0.9f);
- assertEquals(216, scaled.getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(0, scaled.getAmplitudes()[1]);
- assertEquals(-1, scaled.getAmplitudes()[2]);
-
- VibrationEffect.Waveform minAmplitude = new VibrationEffect.Waveform(
- new long[]{100}, new int[] {1}, -1);
- assertArrayEquals(new int[]{1}, minAmplitude.scale(0.5f).getAmplitudes());
+ assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
}
@Test
public void testResolveWaveform() {
- VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM;
- VibrationEffect.Waveform resolved = initial.resolve(123);
- assertNotSame(initial, resolved);
- assertArrayEquals(new int[]{255, 0, 123}, resolved.getAmplitudes());
+ VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
+ assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
- // Ignores input when amplitude already set.
- VibrationEffect.Waveform resolved2 = resolved.resolve(10);
- assertSame(resolved, resolved2);
- assertArrayEquals(new int[]{255, 0, 123}, resolved2.getAmplitudes());
+ assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
}
@Test
- public void testResolveWaveformFailsWhenMaxAmplitudeAboveThreshold() {
- try {
- TEST_WAVEFORM.resolve(1000);
- fail("Max amplitude above threshold, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
+ public void testResolvePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ assertEquals(effect, effect.resolve(51));
+ }
+
+ @Test
+ public void testResolveComposed() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
+ .compose();
+ assertEquals(effect, effect.resolve(51));
+
+ VibrationEffect.Composed resolved = VibrationEffect.startComposition()
+ .addEffect(DEFAULT_ONE_SHOT)
+ .compose()
+ .resolve(51);
+ assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testApplyEffectStrengthOneShot() {
+ VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength(
+ VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(DEFAULT_ONE_SHOT, applied);
+ }
+
+ @Test
+ public void testApplyEffectStrengthWaveform() {
+ VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength(
+ VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(TEST_WAVEFORM, applied);
+ }
+
+ @Test
+ public void testApplyEffectStrengthPrebaked() {
+ VibrationEffect.Composed applied = VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
+ .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
+ ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength());
+ }
+
+ @Test
+ public void testApplyEffectStrengthComposed() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+ .compose();
+ assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT));
+
+ VibrationEffect.Composed applied = VibrationEffect.startComposition()
+ .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .compose()
+ .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
+ ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength());
+ }
+
+ @Test
+ public void testScaleOneShot() {
+ VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
+ assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+
+ VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
+ assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScaleWaveform() {
+ VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
+ assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+
+ VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
+ assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScalePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ assertEquals(effect, scaledUp);
+
+ VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ assertEquals(effect, scaledDown);
}
@Test
public void testScaleComposed() {
- VibrationEffect.Composed initial = (VibrationEffect.Composed) TEST_COMPOSED;
-
- VibrationEffect.Composed copied = initial.scale(1);
- assertEquals(1f, copied.getPrimitiveEffects().get(0).scale);
- assertEquals(0.5f, copied.getPrimitiveEffects().get(1).scale);
- assertEquals(0f, copied.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed halved = initial.scale(0.5f);
- assertEquals(0.34f, halved.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0.17f, halved.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0f, halved.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed scaledUp = initial.scale(1.5f);
- // Does not scale up from 1.
- assertEquals(1f, scaledUp.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- assertTrue(0.5f < scaledUp.getPrimitiveEffects().get(1).scale);
- assertEquals(0f, scaledUp.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed restored = scaledUp.scale(2 / 3f);
- // The original value was not scaled up, so this only scales it down.
- assertEquals(0.53f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(0.47f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0f, restored.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed scaledDown = initial.scale(0.8f);
- assertTrue(1f > scaledDown.getPrimitiveEffects().get(0).scale);
- assertTrue(0.5f > scaledDown.getPrimitiveEffects().get(1).scale);
- assertEquals(0f, scaledDown.getPrimitiveEffects().get(2).scale);
-
- restored = scaledDown.scale(1.25f);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(0.84f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0.5f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0f, restored.getPrimitiveEffects().get(2).scale);
- }
-
- @Test
- public void testResolveComposed_ignoresDefaultAmplitudeAndReturnsSameEffect() {
- VibrationEffect initial = TEST_COMPOSED;
- assertSame(initial, initial.resolve(1000));
- }
-
- @Test
- public void testScaleAppliesSameAdjustmentsOnAllEffects() {
- VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(TEST_TIMING, TEST_AMPLITUDE);
- VibrationEffect.Waveform waveform = new VibrationEffect.Waveform(
- new long[] { TEST_TIMING }, new int[]{ TEST_AMPLITUDE }, -1);
- VibrationEffect.Composed composed =
+ VibrationEffect.Composed effect =
(VibrationEffect.Composed) VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK,
- TEST_AMPLITUDE / 255f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+ .addEffect(TEST_ONE_SHOT)
.compose();
- assertEquals(oneShot.scale(0.8f).getAmplitude(),
- waveform.scale(0.8f).getAmplitudes()[0],
- AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(oneShot.scale(1.2f).getAmplitude() / 255f,
- composed.scale(1.2f).getPrimitiveEffects().get(0).scale,
- INTENSITY_SCALE_TOLERANCE);
- }
+ VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
+ assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude());
- @Test
- public void testScaleOnMaxAmplitude() {
- VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(
- TEST_TIMING, VibrationEffect.MAX_AMPLITUDE);
- VibrationEffect.Waveform waveform = new VibrationEffect.Waveform(
- new long[]{TEST_TIMING}, new int[]{VibrationEffect.MAX_AMPLITUDE}, -1);
- VibrationEffect.Composed composed =
- (VibrationEffect.Composed) VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
- .compose();
-
- // Scale up does NOT scale MAX_AMPLITUDE
- assertEquals(VibrationEffect.MAX_AMPLITUDE, oneShot.scale(1.1f).getAmplitude());
- assertEquals(VibrationEffect.MAX_AMPLITUDE, waveform.scale(1.2f).getAmplitudes()[0]);
- assertEquals(1f,
- composed.scale(1.4f).getPrimitiveEffects().get(0).scale,
- INTENSITY_SCALE_TOLERANCE); // This needs tolerance for float point comparison.
-
- // Scale down does scale MAX_AMPLITUDE
- assertEquals(216, oneShot.scale(0.9f).getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(180, waveform.scale(0.8f).getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(0.57f, composed.scale(0.7f).getPrimitiveEffects().get(0).scale,
- INTENSITY_SCALE_TOLERANCE);
- }
-
- @Test
- public void getEffectStrength_returnsValueFromConstructor() {
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_LIGHT, null);
- Assert.assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, effect.getEffectStrength());
- }
-
- @Test
- public void getFallbackEffect_withFallbackDisabled_isNull() {
- VibrationEffect fallback = VibrationEffect.createOneShot(100, 100);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- false, VibrationEffect.EFFECT_STRENGTH_LIGHT);
- Assert.assertNull(effect.getFallbackEffect());
- }
-
- @Test
- public void getFallbackEffect_withoutEffectSet_isNull() {
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- true, VibrationEffect.EFFECT_STRENGTH_LIGHT);
- Assert.assertNull(effect.getFallbackEffect());
- }
-
- @Test
- public void getFallbackEffect_withFallback_returnsValueFromConstructor() {
- VibrationEffect fallback = VibrationEffect.createOneShot(100, 100);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_LIGHT, fallback);
- Assert.assertEquals(fallback, effect.getFallbackEffect());
+ VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
+ assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
}
private Resources mockRingtoneResources() {
@@ -448,4 +342,4 @@
return context;
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index 09c36dd..40fc00a 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -23,6 +23,7 @@
import android.hardware.vibrator.IVibrator;
import android.platform.test.annotations.Presubmit;
+import android.util.Range;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +32,20 @@
@Presubmit
@RunWith(JUnit4.class)
public class VibratorInfoTest {
+ private static final float TEST_TOLERANCE = 1e-5f;
+
+ private static final float TEST_MIN_FREQUENCY = 50;
+ private static final float TEST_RESONANT_FREQUENCY = 150;
+ private static final float TEST_FREQUENCY_RESOLUTION = 25;
+ private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+
+ private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
+ new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
+ private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
+ new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY,
+ TEST_RESONANT_FREQUENCY, TEST_FREQUENCY_RESOLUTION,
+ /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP);
@Test
public void testHasAmplitudeControl() {
@@ -83,6 +98,139 @@
}
@Test
+ public void testGetFrequencyRange_invalidFrequencyMappingReturnsEmptyRange() {
+ // Invalid, contains NaN values or empty array.
+ assertEquals(Range.create(0f, 0f), new InfoBuilder().build().getFrequencyRange());
+ assertEquals(Range.create(0f, 0f), new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ Float.NaN, 150, 25, 50, TEST_AMPLITUDE_MAP))
+ .build().getFrequencyRange());
+ assertEquals(Range.create(0f, 0f), new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ 50, Float.NaN, 25, 50, TEST_AMPLITUDE_MAP))
+ .build().getFrequencyRange());
+ assertEquals(Range.create(0f, 0f), new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ 50, 150, Float.NaN, 50, TEST_AMPLITUDE_MAP))
+ .build().getFrequencyRange());
+ assertEquals(Range.create(0f, 0f), new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ 50, 150, 25, Float.NaN, TEST_AMPLITUDE_MAP))
+ .build().getFrequencyRange());
+ assertEquals(Range.create(0f, 0f), new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(50, 150, 25, 50, null))
+ .build().getFrequencyRange());
+ // Invalid, minFrequency > resonantFrequency
+ assertEquals(Range.create(0f, 0f), new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ /* minFrequencyHz= */ 250, /* resonantFrequency= */ 150, 25, 50, null))
+ .build().getFrequencyRange());
+ // Invalid, maxFrequency < resonantFrequency by changing resolution.
+ assertEquals(Range.create(0f, 0f), new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ 50, 150, /* frequencyResolutionHz= */10, 50, null))
+ .build().getFrequencyRange());
+ }
+
+ @Test
+ public void testGetFrequencyRange_safeRangeLimitedByMaxFrequency() {
+ VibratorInfo info = new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150,
+ /* frequencyResolutionHz= */ 25, /* suggestedSafeRangeHz= */ 200,
+ TEST_AMPLITUDE_MAP))
+ .build();
+
+ // Mapping should range from 50Hz = -2 to 200Hz = 1
+ // Safe range [-1, 1] = [100Hz, 200Hz] defined by max - resonant = 50Hz
+ assertEquals(Range.create(-2f, 1f), info.getFrequencyRange());
+ }
+
+ @Test
+ public void testGetFrequencyRange_safeRangeLimitedByMinFrequency() {
+ VibratorInfo info = new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150,
+ /* frequencyResolutionHz= */ 50, /* suggestedSafeRangeHz= */ 200,
+ TEST_AMPLITUDE_MAP))
+ .build();
+
+ // Mapping should range from 50Hz = -1 to 350Hz = 2
+ // Safe range [-1, 1] = [50Hz, 250Hz] defined by resonant - min = 100Hz
+ assertEquals(Range.create(-1f, 2f), info.getFrequencyRange());
+ }
+
+ @Test
+ public void testGetFrequencyRange_validMappingReturnsFullRelativeRange() {
+ VibratorInfo info = new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150,
+ /* frequencyResolutionHz= */ 50, /* suggestedSafeRangeHz= */ 100,
+ TEST_AMPLITUDE_MAP))
+ .build();
+
+ // Mapping should range from 50Hz = -2 to 350Hz = 4
+ // Safe range [-1, 1] = [100Hz, 200Hz] defined by suggested safe range 100Hz
+ assertEquals(Range.create(-2f, 4f), info.getFrequencyRange());
+ }
+
+ @Test
+ public void testAbsoluteFrequency_emptyMappingReturnsNaN() {
+ VibratorInfo info = new InfoBuilder().build();
+ assertTrue(Float.isNaN(info.getAbsoluteFrequency(-1)));
+ assertTrue(Float.isNaN(info.getAbsoluteFrequency(0)));
+ assertTrue(Float.isNaN(info.getAbsoluteFrequency(1)));
+ }
+
+ @Test
+ public void testAbsoluteFrequency_validRangeReturnsOriginalValue() {
+ VibratorInfo info = new InfoBuilder().setFrequencyMapping(TEST_FREQUENCY_MAPPING).build();
+ assertEquals(TEST_RESONANT_FREQUENCY, info.getAbsoluteFrequency(0), TEST_TOLERANCE);
+
+ // Safe range [-1, 1] = [125Hz, 175Hz] defined by suggested safe range 100Hz
+ assertEquals(125, info.getAbsoluteFrequency(-1), TEST_TOLERANCE);
+ assertEquals(175, info.getAbsoluteFrequency(1), TEST_TOLERANCE);
+ assertEquals(155, info.getAbsoluteFrequency(0.2f), TEST_TOLERANCE);
+ assertEquals(140, info.getAbsoluteFrequency(-0.4f), TEST_TOLERANCE);
+
+ // Full range [-4, 2] = [50Hz, 200Hz] defined by min frequency and amplitude mapping size
+ assertEquals(50, info.getAbsoluteFrequency(info.getFrequencyRange().getLower()),
+ TEST_TOLERANCE);
+ assertEquals(200, info.getAbsoluteFrequency(info.getFrequencyRange().getUpper()),
+ TEST_TOLERANCE);
+ }
+
+ @Test
+ public void testGetMaxAmplitude_emptyMappingReturnsOnlyResonantFrequency() {
+ VibratorInfo info = new InfoBuilder().build();
+ assertEquals(1f, info.getMaxAmplitude(0), TEST_TOLERANCE);
+ assertEquals(0f, info.getMaxAmplitude(0.1f), TEST_TOLERANCE);
+ assertEquals(0f, info.getMaxAmplitude(-1), TEST_TOLERANCE);
+ }
+
+ @Test
+ public void testGetMaxAmplitude_validMappingReturnsMappedValues() {
+ VibratorInfo info = new InfoBuilder()
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(/* minFrequencyHz= */ 50,
+ /* resonantFrequencyHz= */ 150, /* frequencyResolutionHz= */ 25,
+ /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP))
+ .build();
+
+ assertEquals(1f, info.getMaxAmplitude(0), TEST_TOLERANCE); // 150Hz
+ assertEquals(0.9f, info.getMaxAmplitude(1), TEST_TOLERANCE); // 175Hz
+ assertEquals(0.8f, info.getMaxAmplitude(-1), TEST_TOLERANCE); // 125Hz
+ assertEquals(0.8f, info.getMaxAmplitude(info.getFrequencyRange().getUpper()),
+ TEST_TOLERANCE); // 200Hz
+ assertEquals(0.1f, info.getMaxAmplitude(info.getFrequencyRange().getLower()),
+ TEST_TOLERANCE); // 50Hz
+
+ // Rounds 145Hz to the max amplitude for 125Hz, which is lower.
+ assertEquals(0.8f, info.getMaxAmplitude(-0.1f), TEST_TOLERANCE); // 145Hz
+ // Rounds 185Hz to the max amplitude for 200Hz, which is lower.
+ assertEquals(0.8f, info.getMaxAmplitude(1.2f), TEST_TOLERANCE); // 185Hz
+ }
+
+ @Test
public void testEquals() {
InfoBuilder completeBuilder = new InfoBuilder()
.setId(1)
@@ -90,7 +238,7 @@
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
.setQFactor(2f)
- .setResonantFrequency(150f);
+ .setFrequencyMapping(TEST_FREQUENCY_MAPPING);
VibratorInfo complete = completeBuilder.build();
assertEquals(complete, complete);
@@ -110,22 +258,24 @@
VibratorInfo completeWithUnknownEffects = completeBuilder
.setSupportedEffects(null)
.build();
- assertNotEquals(complete, completeWithNoEffects);
+ assertNotEquals(complete, completeWithUnknownEffects);
VibratorInfo completeWithUnknownPrimitives = completeBuilder
.setSupportedPrimitives(null)
.build();
assertNotEquals(complete, completeWithUnknownPrimitives);
- VibratorInfo completeWithDifferentF0 = completeBuilder
- .setResonantFrequency(complete.getResonantFrequency() + 3f)
+ VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder
+ .setFrequencyMapping(new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY + 10,
+ TEST_RESONANT_FREQUENCY + 20, TEST_FREQUENCY_RESOLUTION + 5,
+ /* suggestedSafeRangeHz= */ 100, TEST_AMPLITUDE_MAP))
.build();
- assertNotEquals(complete, completeWithDifferentF0);
+ assertNotEquals(complete, completeWithDifferentFrequencyMapping);
- VibratorInfo completeWithUnknownF0 = completeBuilder
- .setResonantFrequency(Float.NaN)
+ VibratorInfo completeWithEmptyFrequencyMapping = completeBuilder
+ .setFrequencyMapping(EMPTY_FREQUENCY_MAPPING)
.build();
- assertNotEquals(complete, completeWithUnknownF0);
+ assertNotEquals(complete, completeWithEmptyFrequencyMapping);
VibratorInfo completeWithUnknownQFactor = completeBuilder
.setQFactor(Float.NaN)
@@ -153,8 +303,8 @@
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitives(null)
- .setResonantFrequency(1.3f)
.setQFactor(Float.NaN)
+ .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
.build();
Parcel parcel = Parcel.obtain();
@@ -169,8 +319,8 @@
private int mCapabilities = 0;
private int[] mSupportedEffects = null;
private int[] mSupportedPrimitives = null;
- private float mResonantFrequency = Float.NaN;
private float mQFactor = Float.NaN;
+ private VibratorInfo.FrequencyMapping mFrequencyMapping = EMPTY_FREQUENCY_MAPPING;
public InfoBuilder setId(int id) {
mId = id;
@@ -192,19 +342,19 @@
return this;
}
- public InfoBuilder setResonantFrequency(float resonantFrequency) {
- mResonantFrequency = resonantFrequency;
- return this;
- }
-
public InfoBuilder setQFactor(float qFactor) {
mQFactor = qFactor;
return this;
}
+ public InfoBuilder setFrequencyMapping(VibratorInfo.FrequencyMapping frequencyMapping) {
+ mFrequencyMapping = frequencyMapping;
+ return this;
+ }
+
public VibratorInfo build() {
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
- mResonantFrequency, mQFactor);
+ mQFactor, mFrequencyMapping);
}
}
}
diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
new file mode 100644
index 0000000..de80812
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class PrebakedSegmentTest {
+
+ @Test
+ public void testCreation() {
+ PrebakedSegment prebaked = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+ assertEquals(-1, prebaked.getDuration());
+ assertTrue(prebaked.hasNonZeroAmplitude());
+ assertEquals(VibrationEffect.EFFECT_CLICK, prebaked.getEffectId());
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_MEDIUM, prebaked.getEffectStrength());
+ assertTrue(prebaked.shouldFallback());
+ }
+
+ @Test
+ public void testSerialization() {
+ PrebakedSegment original = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(original, PrebakedSegment.CREATOR.createFromParcel(parcel));
+ }
+
+ @Test
+ public void testValidate() {
+ new PrebakedSegment(VibrationEffect.EFFECT_CLICK, true,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrebakedSegment(1000, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrebakedSegment(VibrationEffect.EFFECT_TICK, false, 1000)
+ .validate());
+ }
+
+ @Test
+ public void testResolve_ignoresAndReturnsSameEffect() {
+ PrebakedSegment prebaked = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertSame(prebaked, prebaked.resolve(1000));
+ }
+
+ @Test
+ public void testApplyEffectStrength() {
+ PrebakedSegment medium = new PrebakedSegment(
+ VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+ PrebakedSegment light = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertNotEquals(medium, light);
+ assertEquals(medium.getEffectId(), light.getEffectId());
+ assertEquals(medium.shouldFallback(), light.shouldFallback());
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, light.getEffectStrength());
+
+ PrebakedSegment strong = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertNotEquals(medium, strong);
+ assertEquals(medium.getEffectId(), strong.getEffectId());
+ assertEquals(medium.shouldFallback(), strong.shouldFallback());
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_STRONG, strong.getEffectStrength());
+
+ assertSame(medium, medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_MEDIUM));
+ // Invalid vibration effect strength is ignored.
+ assertSame(medium, medium.applyEffectStrength(1000));
+ }
+
+ @Test
+ public void testScale_ignoresAndReturnsSameEffect() {
+ PrebakedSegment prebaked = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertSame(prebaked, prebaked.scale(0.5f));
+ }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
new file mode 100644
index 0000000..538655b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class PrimitiveSegmentTest {
+ private static final float TOLERANCE = 1e-2f;
+
+ @Test
+ public void testCreation() {
+ PrimitiveSegment primitive = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
+
+ assertEquals(-1, primitive.getDuration());
+ assertTrue(primitive.hasNonZeroAmplitude());
+ assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId());
+ assertEquals(10, primitive.getDelay());
+ assertEquals(1f, primitive.getScale(), TOLERANCE);
+ }
+
+ @Test
+ public void testSerialization() {
+ PrimitiveSegment original = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(original, PrimitiveSegment.CREATOR.createFromParcel(parcel));
+ }
+
+ @Test
+ public void testValidate() {
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrimitiveSegment(1000, 0, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, -1, 0)
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, 1, -1)
+ .validate());
+ }
+
+ @Test
+ public void testResolve_ignoresAndReturnsSameEffect() {
+ PrimitiveSegment primitive = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+ assertSame(primitive, primitive.resolve(1000));
+ }
+
+ @Test
+ public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+ PrimitiveSegment primitive = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+ assertSame(primitive,
+ primitive.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ }
+
+ @Test
+ public void testScale_fullPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+ assertEquals(1f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0.34f, initial.scale(0.5f).getScale(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.71f, initial.scale(0.8f).getScale(), TOLERANCE);
+ assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_halfPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);
+
+ assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0.17f, initial.scale(0.5f).getScale(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.86f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.35f, initial.scale(0.8f).getScale(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_zeroPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
+
+ assertEquals(0f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.5f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
new file mode 100644
index 0000000..174b4a7
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class RampSegmentTest {
+ private static final float TOLERANCE = 1e-2f;
+
+ @Test
+ public void testCreation() {
+ RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+ /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100);
+
+ assertEquals(100L, ramp.getDuration());
+ assertTrue(ramp.hasNonZeroAmplitude());
+ assertEquals(1f, ramp.getStartAmplitude());
+ assertEquals(0f, ramp.getEndAmplitude());
+ assertEquals(-1f, ramp.getStartFrequency());
+ assertEquals(1f, ramp.getEndFrequency());
+ }
+
+ @Test
+ public void testSerialization() {
+ RampSegment original = new RampSegment(0, 1, 0, 0.5f, 10);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(original, RampSegment.CREATOR.createFromParcel(parcel));
+ }
+
+ @Test
+ public void testValidate() {
+ new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+ /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new RampSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0, 0, 0).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new RampSegment(/* startAmplitude= */ -2, 0, 0, 0, 0).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new RampSegment(0, /* endAmplitude= */ 2, 0, 0, 0).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new RampSegment(0, 0, 0, 0, /* duration= */ -1).validate());
+ }
+
+ @Test
+ public void testHasNonZeroAmplitude() {
+ assertTrue(new RampSegment(0, 1, 0, 0, 0).hasNonZeroAmplitude());
+ assertTrue(new RampSegment(0.01f, 0, 0, 0, 0).hasNonZeroAmplitude());
+ assertFalse(new RampSegment(0, 0, 0, 0, 0).hasNonZeroAmplitude());
+ }
+
+ @Test
+ public void testResolve() {
+ RampSegment ramp = new RampSegment(0, 1, 0, 0, 0);
+ assertSame(ramp, ramp.resolve(100));
+ }
+
+ @Test
+ public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+ RampSegment ramp = new RampSegment(1, 0, 1, 0, 0);
+ assertSame(ramp, ramp.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ }
+
+ @Test
+ public void testScale() {
+ RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+
+ assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+
+ assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE);
+ assertEquals(0.34f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getEndAmplitude(), TOLERANCE);
+ assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getEndAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.71f, initial.scale(0.8f).getEndAmplitude(), TOLERANCE);
+ assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getEndAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_halfPrimitiveScaleValue() {
+ RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0);
+
+ assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
new file mode 100644
index 0000000..79529b8
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 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.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class StepSegmentTest {
+ private static final float TOLERANCE = 1e-2f;
+
+ @Test
+ public void testCreation() {
+ StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequency= */ -1f,
+ /* duration= */ 100);
+
+ assertEquals(100, step.getDuration());
+ assertTrue(step.hasNonZeroAmplitude());
+ assertEquals(1f, step.getAmplitude());
+ assertEquals(-1f, step.getFrequency());
+ }
+
+ @Test
+ public void testSerialization() {
+ StepSegment original = new StepSegment(0.5f, 1f, 10);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(original, StepSegment.CREATOR.createFromParcel(parcel));
+ }
+
+ @Test
+ public void testValidate() {
+ new StepSegment(/* amplitude= */ 0f, /* frequency= */ -1f, /* duration= */ 100).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(/* amplitude= */ -2, 1f, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(/* amplitude= */ 2, 1f, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(2, 1f, /* duration= */ -1).validate());
+ }
+
+ @Test
+ public void testHasNonZeroAmplitude() {
+ assertTrue(new StepSegment(1f, 0, 0).hasNonZeroAmplitude());
+ assertTrue(new StepSegment(0.01f, 0, 0).hasNonZeroAmplitude());
+ assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0).hasNonZeroAmplitude());
+ assertFalse(new StepSegment(0, 0, 0).hasNonZeroAmplitude());
+ }
+
+ @Test
+ public void testResolve() {
+ StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
+ assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude());
+ assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE);
+
+ StepSegment resolved = new StepSegment(0, 0, 0);
+ assertSame(resolved, resolved.resolve(100));
+
+ assertThrows(IllegalArgumentException.class, () -> resolved.resolve(1000));
+ }
+
+ @Test
+ public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+ StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
+ assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ }
+
+ @Test
+ public void testScale_fullAmplitude() {
+ StepSegment initial = new StepSegment(1f, 0, 0);
+
+ assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0.34f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.71f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+ assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_halfAmplitude() {
+ StepSegment initial = new StepSegment(0.5f, 0, 0);
+
+ assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0.17f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.86f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.35f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_zeroAmplitude() {
+ StepSegment initial = new StepSegment(0, 0, 0);
+
+ assertEquals(0f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_defaultAmplitude() {
+ StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
+
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(0.5f).getAmplitude(),
+ TOLERANCE);
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(),
+ TOLERANCE);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 33b8aed..fc3be13 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -68,7 +68,8 @@
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1)
.setDischargePercentage(20)
- .setDischargedPowerRange(1000, 2000);
+ .setDischargedPowerRange(1000, 2000)
+ .setStatsStartTimestamp(1000);
builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid)
.setPackageWithHighestDrain("foo")
@@ -105,6 +106,7 @@
assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20);
assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000);
assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000);
+ assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(1000);
final List<UidBatteryConsumer> uidBatteryConsumers =
batteryUsageStats.getUidBatteryConsumers();
diff --git a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml
index 0898fae..b3b34ef 100644
--- a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml
@@ -32,6 +32,8 @@
<uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BRICK" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED" />
<uses-permission android:name="android.permission.BROADCAST_SMS" />
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml
index 98f7177..42d9407 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml
@@ -21,6 +21,9 @@
android:sharedUserId="com.android.framework.externalsharedpermstestapp">
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 27bf4ef..3213390 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -48,6 +48,10 @@
<group gid="uhid" />
</permission>
+ <permission name="android.permission.VIRTUAL_INPUT_DEVICE" >
+ <group gid="uhid" />
+ </permission>
+
<permission name="android.permission.NET_TUNNELING" >
<group gid="vpn" />
</permission>
@@ -224,7 +228,22 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
</split-permission>
-
+ <split-permission name="android.permission.BLUETOOTH"
+ targetSdk="31">
+ <new-permission name="android.permission.BLUETOOTH_SCAN" />
+ </split-permission>
+ <split-permission name="android.permission.BLUETOOTH"
+ targetSdk="31">
+ <new-permission name="android.permission.BLUETOOTH_CONNECT" />
+ </split-permission>
+ <split-permission name="android.permission.BLUETOOTH_ADMIN"
+ targetSdk="31">
+ <new-permission name="android.permission.BLUETOOTH_SCAN" />
+ </split-permission>
+ <split-permission name="android.permission.BLUETOOTH_ADMIN"
+ targetSdk="31">
+ <new-permission name="android.permission.BLUETOOTH_CONNECT" />
+ </split-permission>
<!-- This is a list of all the libraries available for application
code to link against. -->
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 2a6bbf3..cafda09 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1441,13 +1441,11 @@
/** @hide */
public boolean isSupportedAxes(int axis) {
- if (mSupportedAxes == null) {
- synchronized (this) {
+ synchronized (this) {
+ if (mSupportedAxes == null) {
+ mSupportedAxes = nativeGetSupportedAxes(native_instance);
if (mSupportedAxes == null) {
- mSupportedAxes = nativeGetSupportedAxes(native_instance);
- if (mSupportedAxes == null) {
- mSupportedAxes = EMPTY_AXES;
- }
+ mSupportedAxes = EMPTY_AXES;
}
}
}
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index cd77d9c..ec3b102 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -254,6 +254,8 @@
keyStore.deleteEntry(keystoreAlias);
return certificateChain;
+ } catch (SecurityException e) {
+ throw e;
} catch (Exception e) {
throw new DeviceIdAttestationException("Unable to perform attestation", e);
}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 5cb2c3b..9ca551b 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -288,7 +288,7 @@
private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
private final String mKeystoreAlias;
- private final int mNamespace;
+ private final @KeyProperties.Namespace int mNamespace;
private final int mKeySize;
private final AlgorithmParameterSpec mSpec;
private final X500Principal mCertificateSubject;
@@ -331,7 +331,7 @@
*/
public KeyGenParameterSpec(
String keyStoreAlias,
- int namespace,
+ @KeyProperties.Namespace int namespace,
int keySize,
AlgorithmParameterSpec spec,
X500Principal certificateSubject,
@@ -472,7 +472,7 @@
* @hide
*/
@SystemApi
- public int getNamespace() {
+ public @KeyProperties.Namespace int getNamespace() {
return mNamespace;
}
@@ -896,7 +896,7 @@
private final String mKeystoreAlias;
private @KeyProperties.PurposeEnum int mPurposes;
- private int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
+ private @KeyProperties.Namespace int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
private int mKeySize = -1;
private AlgorithmParameterSpec mSpec;
private X500Principal mCertificateSubject;
@@ -1051,7 +1051,7 @@
*/
@SystemApi
@NonNull
- public Builder setNamespace(int namespace) {
+ public Builder setNamespace(@KeyProperties.Namespace int namespace) {
mNamespace = namespace;
return this;
}
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 7b0fa91..682d12a 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -892,6 +892,22 @@
}
/**
+ * Namespaces provide system developers and vendors with a way to use keystore without
+ * requiring an applications uid. Namespaces can be configured using SEPolicy.
+ * See <a href="https://source.android.com/security/keystore#access-control">
+ * Keystore 2.0 access-control</a>
+ * {@See KeyGenParameterSpec.Builder#setNamespace}
+ * {@See android.security.keystore2.AndroidKeyStoreLoadStoreParameter}
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NAMESPACE_" }, value = {
+ NAMESPACE_APPLICATION,
+ NAMESPACE_WIFI,
+ })
+ public @interface Namespace {}
+
+ /**
* This value indicates the implicit keystore namespace of the calling application.
* It is used by default. Only select system components can choose a different namespace
* which it must be configured in SEPolicy.
@@ -912,14 +928,12 @@
* For legacy support, translate namespaces into known UIDs.
* @hide
*/
- public static int namespaceToLegacyUid(int namespace) {
+ public static int namespaceToLegacyUid(@Namespace int namespace) {
switch (namespace) {
case NAMESPACE_APPLICATION:
return KeyStore.UID_SELF;
case NAMESPACE_WIFI:
return Process.WIFI_UID;
- // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
- // b/171305388 and b/171305607
default:
throw new IllegalArgumentException("No UID corresponding to namespace "
+ namespace);
@@ -930,14 +944,12 @@
* For legacy support, translate namespaces into known UIDs.
* @hide
*/
- public static int legacyUidToNamespace(int uid) {
+ public static @Namespace int legacyUidToNamespace(int uid) {
switch (uid) {
case KeyStore.UID_SELF:
return NAMESPACE_APPLICATION;
case Process.WIFI_UID:
return NAMESPACE_WIFI;
- // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
- // b/171305388 and b/171305607
default:
throw new IllegalArgumentException("No namespace corresponding to uid "
+ uid);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
index 0c6744f..25b1c86 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
@@ -16,6 +16,8 @@
package android.security.keystore2;
+import android.security.keystore.KeyProperties;
+
import java.security.KeyStore;
import java.security.KeyStore.ProtectionParameter;
@@ -24,9 +26,9 @@
*/
public class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter {
- private final int mNamespace;
+ private final @KeyProperties.Namespace int mNamespace;
- public AndroidKeyStoreLoadStoreParameter(int namespace) {
+ public AndroidKeyStoreLoadStoreParameter(@KeyProperties.Namespace int namespace) {
mNamespace = namespace;
}
@@ -35,7 +37,7 @@
return null;
}
- int getNamespace() {
+ @KeyProperties.Namespace int getNamespace() {
return mNamespace;
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 39607ae..32f98a2 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -100,7 +100,7 @@
public static final String NAME = "AndroidKeyStore";
private KeyStore2 mKeyStore;
- private int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
+ private @KeyProperties.Namespace int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
@@ -1125,7 +1125,7 @@
@Override
public void engineLoad(LoadStoreParameter param) throws IOException,
NoSuchAlgorithmException, CertificateException {
- int namespace = KeyProperties.NAMESPACE_APPLICATION;
+ @KeyProperties.Namespace int namespace = KeyProperties.NAMESPACE_APPLICATION;
if (param != null) {
if (param instanceof AndroidKeyStoreLoadStoreParameter) {
namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 3708e15..34c66a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -16,6 +16,8 @@
package com.android.wm.shell;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
+
import android.annotation.UiContext;
import android.app.ResourcesManager;
import android.content.Context;
@@ -34,6 +36,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -44,14 +48,14 @@
private static final String TAG = RootTaskDisplayAreaOrganizer.class.getSimpleName();
- // Display area info. mapped by displayIds.
+ /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
- // Display area leashes. mapped by displayIds.
+ /** Display area leashes, which is mapped by display IDs. */
private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
private final SparseArray<ArrayList<RootTaskDisplayAreaListener>> mListeners =
new SparseArray<>();
-
+ /** {@link DisplayAreaContext} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();
private final Context mContext;
@@ -173,8 +177,9 @@
final Display display = mContext.getSystemService(DisplayManager.class)
.getDisplay(displayId);
if (display == null) {
- throw new UnsupportedOperationException("The display #" + displayId + " is invalid."
- + "displayAreaInfo:" + displayAreaInfo);
+ ProtoLog.w(WM_SHELL_TASK_ORG, "The display#%d has been removed."
+ + " Skip following steps", displayId);
+ return;
}
DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
if (daContext == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 18c6b66..28c8f53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -121,8 +121,8 @@
} else if (mTmpAttrs.mWindowBgResId != 0) {
themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
} else {
- Slog.w(TAG, "Window background not exist!");
themeBGDrawable = createDefaultBackgroundDrawable();
+ Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
}
final int estimatedWindowBGColor = estimateWindowBGColor(themeBGDrawable);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
diff --git a/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
index 50ed046..092ec67f 100644
--- a/location/java/android/location/provider/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -24,7 +24,7 @@
* @hide
*/
interface ILocationProviderManager {
- void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String packageName, @nullable String attributionTag);
+ void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String attributionTag);
void onSetAllowed(boolean allowed);
void onSetProperties(in ProviderProperties properties);
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index ae6395d..eada22c 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
"com.android.location.service.FusedLocationProvider";
private final String mTag;
- private final @Nullable String mPackageName;
private final @Nullable String mAttributionTag;
private final IBinder mBinder;
@@ -108,7 +107,6 @@
public LocationProviderBase(@NonNull Context context, @NonNull String tag,
@NonNull ProviderProperties properties) {
mTag = tag;
- mPackageName = context.getPackageName();
mAttributionTag = context.getAttributionTag();
mBinder = new Service();
@@ -305,7 +303,7 @@
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
- manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+ manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 7f1cf6d..95f6c2f 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
final String mTag;
- @Nullable final String mPackageName;
@Nullable final String mAttributionTag;
final IBinder mBinder;
@@ -133,7 +132,6 @@
public LocationProviderBase(Context context, String tag,
ProviderPropertiesUnbundled properties) {
mTag = tag;
- mPackageName = context != null ? context.getPackageName() : null;
mAttributionTag = context != null ? context.getAttributionTag() : null;
mBinder = new Service();
@@ -370,7 +368,7 @@
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
- manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+ manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index 9774e80..ac96e6f 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -88,7 +88,7 @@
if (ints == null || ints.length == 0) {
return "";
}
- return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X, ", anInt))
- .collect(Collectors.joining());
+ return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X", anInt))
+ .collect(Collectors.joining(", "));
}
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index cf31e41..a7e2b65 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1674,9 +1674,11 @@
public @interface BufferFlag {}
private EventHandler mEventHandler;
+ private EventHandler mOnFirstTunnelFrameReadyHandler;
private EventHandler mOnFrameRenderedHandler;
private EventHandler mCallbackHandler;
private Callback mCallback;
+ private OnFirstTunnelFrameReadyListener mOnFirstTunnelFrameReadyListener;
private OnFrameRenderedListener mOnFrameRenderedListener;
private final Object mListenerLock = new Object();
private MediaCodecInfo mCodecInfo;
@@ -1687,6 +1689,7 @@
private static final int EVENT_CALLBACK = 1;
private static final int EVENT_SET_CALLBACK = 2;
private static final int EVENT_FRAME_RENDERED = 3;
+ private static final int EVENT_FIRST_TUNNEL_FRAME_READY = 4;
private static final int CB_INPUT_AVAILABLE = 1;
private static final int CB_OUTPUT_AVAILABLE = 2;
@@ -1748,6 +1751,16 @@
mCodec, (long)mediaTimeUs, (long)systemNano);
}
break;
+ case EVENT_FIRST_TUNNEL_FRAME_READY:
+ OnFirstTunnelFrameReadyListener onFirstTunnelFrameReadyListener;
+ synchronized (mListenerLock) {
+ onFirstTunnelFrameReadyListener = mOnFirstTunnelFrameReadyListener;
+ }
+ if (onFirstTunnelFrameReadyListener == null) {
+ break;
+ }
+ onFirstTunnelFrameReadyListener.onFirstTunnelFrameReady(mCodec);
+ break;
default:
{
break;
@@ -1923,6 +1936,7 @@
mEventHandler = null;
}
mCallbackHandler = mEventHandler;
+ mOnFirstTunnelFrameReadyHandler = mEventHandler;
mOnFrameRenderedHandler = mEventHandler;
mBufferLock = new Object();
@@ -2277,6 +2291,9 @@
mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
mCallbackHandler.removeMessages(EVENT_CALLBACK);
}
+ if (mOnFirstTunnelFrameReadyHandler != null) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
if (mOnFrameRenderedHandler != null) {
mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
}
@@ -4447,6 +4464,41 @@
MediaFormat.KEY_LOW_LATENCY;
/**
+ * Control video peek of the first frame when a codec is configured for tunnel mode with
+ * {@link MediaFormat#KEY_AUDIO_SESSION_ID} while the {@link AudioTrack} is paused.
+ *<p>
+ * When disabled (1) after a {@link #flush} or {@link #start}, (2) while the corresponding
+ * {@link AudioTrack} is paused and (3) before any buffers are queued, the first frame is not to
+ * be rendered until either this parameter is enabled or the corresponding {@link AudioTrack}
+ * has begun playback. Once the frame is decoded and ready to be rendered,
+ * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called but the frame is
+ * not rendered. The surface continues to show the previously-rendered content, or black if the
+ * surface is new. A subsequent call to {@link AudioTrack#play} renders this frame and triggers
+ * a callback to {@link OnFrameRenderedListener#onFrameRendered}, and video playback begins.
+ *<p>
+ * <b>Note</b>: To clear any previously rendered content and show black, configure the
+ * MediaCodec with {@code KEY_PUSH_BLANK_BUFFERS_ON_STOP(1)}, and call {@link #stop} before
+ * pushing new video frames to the codec.
+ *<p>
+ * When enabled (1) after a {@link #flush} or {@link #start} and (2) while the corresponding
+ * {@link AudioTrack} is paused, the first frame is rendered as soon as it is decoded, or
+ * immediately, if it has already been decoded. If not already decoded, when the frame is
+ * decoded and ready to be rendered,
+ * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called. The frame is then
+ * immediately rendered and {@link OnFrameRenderedListener#onFrameRendered} is subsequently
+ * called.
+ *<p>
+ * The value is an Integer object containing the value 1 to enable or the value 0 to disable.
+ *<p>
+ * The default for this parameter is <b>enabled</b>. Once a frame has been rendered, changing
+ * this parameter has no effect until a subsequent {@link #flush} or
+ * {@link #stop}/{@link #start}.
+ *
+ * @see #setParameters(Bundle)
+ */
+ public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek";
+
+ /**
* Communicate additional parameter changes to the component instance.
* <b>Note:</b> Some of these parameter changes may silently fail to apply.
*
@@ -4545,6 +4597,55 @@
}
/**
+ * Listener to be called when the first output frame has been decoded
+ * and is ready to be rendered for a codec configured for tunnel mode with
+ * {@code KEY_AUDIO_SESSION_ID}.
+ *
+ * @see MediaCodec#setOnFirstTunnelFrameReadyListener
+ */
+ public interface OnFirstTunnelFrameReadyListener {
+
+ /**
+ * Called when the first output frame has been decoded and is ready to be
+ * rendered.
+ */
+ void onFirstTunnelFrameReady(@NonNull MediaCodec codec);
+ }
+
+ /**
+ * Registers a callback to be invoked when the first output frame has been decoded
+ * and is ready to be rendered on a codec configured for tunnel mode with {@code
+ * KEY_AUDIO_SESSION_ID}.
+ *
+ * @param handler the callback will be run on the handler's thread. If {@code
+ * null}, the callback will be run on the default thread, which is the looper from
+ * which the codec was created, or a new thread if there was none.
+ *
+ * @param listener the callback that will be run. If {@code null}, clears any registered
+ * listener.
+ */
+ public void setOnFirstTunnelFrameReadyListener(
+ @Nullable Handler handler, @Nullable OnFirstTunnelFrameReadyListener listener) {
+ synchronized (mListenerLock) {
+ mOnFirstTunnelFrameReadyListener = listener;
+ if (listener != null) {
+ EventHandler newHandler = getEventHandlerOn(
+ handler,
+ mOnFirstTunnelFrameReadyHandler);
+ if (newHandler != mOnFirstTunnelFrameReadyHandler) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
+ mOnFirstTunnelFrameReadyHandler = newHandler;
+ } else if (mOnFirstTunnelFrameReadyHandler != null) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
+ native_enableOnFirstTunnelFrameReadyListener(listener != null);
+ }
+ }
+
+ private native void native_enableOnFirstTunnelFrameReadyListener(boolean enable);
+
+ /**
* Listener to be called when an output frame has rendered on the output surface
*
* @see MediaCodec#setOnFrameRenderedListener
@@ -4667,6 +4768,8 @@
EventHandler handler = mEventHandler;
if (what == EVENT_CALLBACK) {
handler = mCallbackHandler;
+ } else if (what == EVENT_FIRST_TUNNEL_FRAME_READY) {
+ handler = mOnFirstTunnelFrameReadyHandler;
} else if (what == EVENT_FRAME_RENDERED) {
handler = mOnFrameRenderedHandler;
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 02fa040..8daa303 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -299,6 +299,24 @@
}
/**
+ * Registers a callback to receive route related events when they change.
+ * <p>
+ * If the specified callback is already registered, its registration will be updated for the
+ * given {@link Executor executor}.
+ * <p>
+ * This will be no-op for non-system routers.
+ * @hide
+ */
+ @SystemApi
+ public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull RouteCallback routeCallback) {
+ if (!isSystemRouter()) {
+ return;
+ }
+ registerRouteCallback(executor, routeCallback, RouteDiscoveryPreference.EMPTY);
+ }
+
+ /**
* Registers a callback to discover routes and to receive events when they change.
* <p>
* If the specified callback is already registered, its registration will be updated for the
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 96bffee117..3b0f577 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -39,6 +39,8 @@
ISession createSession(String packageName, in ISessionCallback sessionCb, String tag,
in Bundle sessionInfo, int userId);
List<MediaSession.Token> getSessions(in ComponentName compName, int userId);
+ MediaSession.Token getMediaKeyEventSession();
+ String getMediaKeyEventSessionPackageName();
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
boolean needWakeLock);
boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 78db750..269b70b 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -195,6 +195,44 @@
}
/**
+ * Gets the media key event session, which would receive a media key event unless specified.
+ * @return The media key event session, which would receive key events by default, unless
+ * the caller has specified the target. Can be {@code null}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @Nullable
+ public MediaSession.Token getMediaKeyEventSession() {
+ try {
+ return mService.getMediaKeyEventSession();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get media key event session", ex);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the package name of the media key event session.
+ * @return The package name of the media key event session or the last session's media button
+ * receiver if the media key event session is {@code null}.
+ * @see #getMediaKeyEventSession()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @NonNull
+ public String getMediaKeyEventSessionPackageName() {
+ try {
+ String packageName = mService.getMediaKeyEventSessionPackageName();
+ return (packageName != null) ? packageName : "";
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get media key event session", ex);
+ }
+ return "";
+ }
+
+ /**
* Get active sessions for the given user.
* <p>
* This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be
@@ -856,7 +894,7 @@
}
/**
- * Add a {@link OnMediaKeyEventDispatchedListener}.
+ * Add a {@link OnMediaKeyEventSessionChangedListener}.
*
* @param executor The executor on which the listener should be invoked
* @param listener A {@link OnMediaKeyEventSessionChangedListener}.
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 7f5dd5d..ded2e1b 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -81,6 +81,7 @@
EVENT_CALLBACK = 1,
EVENT_SET_CALLBACK = 2,
EVENT_FRAME_RENDERED = 3,
+ EVENT_FIRST_TUNNEL_FRAME_READY = 4,
};
static struct CryptoErrorCodes {
@@ -269,6 +270,18 @@
mClass = NULL;
}
+status_t JMediaCodec::enableOnFirstTunnelFrameReadyListener(jboolean enable) {
+ if (enable) {
+ if (mOnFirstTunnelFrameReadyNotification == NULL) {
+ mOnFirstTunnelFrameReadyNotification = new AMessage(kWhatFirstTunnelFrameReady, this);
+ }
+ } else {
+ mOnFirstTunnelFrameReadyNotification.clear();
+ }
+
+ return mCodec->setOnFirstTunnelFrameReadyNotification(mOnFirstTunnelFrameReadyNotification);
+}
+
status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) {
if (enable) {
if (mOnFrameRenderedNotification == NULL) {
@@ -1058,6 +1071,27 @@
env->DeleteLocalRef(obj);
}
+void JMediaCodec::handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg) {
+ int32_t arg1 = 0, arg2 = 0;
+ jobject obj = NULL;
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ sp<AMessage> data;
+ CHECK(msg->findMessage("data", &data));
+
+ status_t err = ConvertMessageToMap(env, data, &obj);
+ if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ env->CallVoidMethod(
+ mObject, gFields.postEventFromNativeID,
+ EVENT_FIRST_TUNNEL_FRAME_READY, arg1, arg2, obj);
+
+ env->DeleteLocalRef(obj);
+}
+
void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) {
int32_t arg1 = 0, arg2 = 0;
jobject obj = NULL;
@@ -1100,6 +1134,11 @@
}
break;
}
+ case kWhatFirstTunnelFrameReady:
+ {
+ handleFirstTunnelFrameReadyNotification(msg);
+ break;
+ }
default:
TRESPASS();
}
@@ -1256,6 +1295,22 @@
}
}
+static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener(
+ JNIEnv *env,
+ jobject thiz,
+ jboolean enabled) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled);
+
+ throwExceptionAsNecessary(env, err);
+}
+
static void android_media_MediaCodec_native_enableOnFrameRenderedListener(
JNIEnv *env,
jobject thiz,
@@ -3138,6 +3193,9 @@
{ "native_setInputSurface", "(Landroid/view/Surface;)V",
(void *)android_media_MediaCodec_setInputSurface },
+ { "native_enableOnFirstTunnelFrameReadyListener", "(Z)V",
+ (void *)android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener },
+
{ "native_enableOnFrameRenderedListener", "(Z)V",
(void *)android_media_MediaCodec_native_enableOnFrameRenderedListener },
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index f16bcf3..5fd6bfd 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -63,6 +63,8 @@
void release();
void releaseAsync();
+ status_t enableOnFirstTunnelFrameReadyListener(jboolean enable);
+
status_t enableOnFrameRenderedListener(jboolean enable);
status_t setCallback(jobject cb);
@@ -176,6 +178,7 @@
kWhatCallbackNotify,
kWhatFrameRendered,
kWhatAsyncReleaseComplete,
+ kWhatFirstTunnelFrameReady,
};
jclass mClass;
@@ -191,6 +194,7 @@
std::once_flag mAsyncReleaseFlag;
sp<AMessage> mCallbackNotification;
+ sp<AMessage> mOnFirstTunnelFrameReadyNotification;
sp<AMessage> mOnFrameRenderedNotification;
status_t mInitStatus;
@@ -203,6 +207,7 @@
jobject *buf) const;
void handleCallback(const sp<AMessage> &msg);
+ void handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg);
void handleFrameRenderedNotification(const sp<AMessage> &msg);
DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
index fc96fd9..3794ccd 100644
--- a/media/packages/BluetoothMidiService/AndroidManifest.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -27,6 +27,9 @@
<uses-feature android:name="android.software.midi"
android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<application
tools:replace="android:label"
diff --git a/media/tests/ScoAudioTest/AndroidManifest.xml b/media/tests/ScoAudioTest/AndroidManifest.xml
index a0fba73..5af77ee 100644
--- a/media/tests/ScoAudioTest/AndroidManifest.xml
+++ b/media/tests/ScoAudioTest/AndroidManifest.xml
@@ -22,6 +22,9 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<application>
<activity android:label="@string/app_name"
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b01878b..6d7badb 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -285,7 +285,10 @@
ATrace_endAsyncSection; # introduced=29
ATrace_setCounter; # introduced=29
android_getaddrinfofornetwork; # introduced=23
+ android_getprocnetwork; # introduced=31
android_setprocnetwork; # introduced=23
+ android_getprocdns; # introduced=31
+ android_setprocdns; # introduced=31
android_setsocknetwork; # introduced=23
android_res_cancel; # introduced=29
android_res_nquery; # introduced=29
@@ -309,4 +312,4 @@
ASurfaceControlStats_getAcquireTime*;
ASurfaceControlStats_getFrameNumber*;
};
-} LIBANDROID;
\ No newline at end of file
+} LIBANDROID;
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index 8d4e900..a6c1b50 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -14,6 +14,10 @@
android_res_nquery; # llndk
android_res_nresult; # llndk
android_res_nsend; # llndk
+ # These functions have been part of the NDK since API 31.
+ android_getprocnetwork; # llndk
+ android_setprocdns; # llndk
+ android_getprocdns; # llndk
local:
*;
};
diff --git a/native/android/net.c b/native/android/net.c
index a8104fc..e2f36a7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -22,12 +22,13 @@
#include <stdlib.h>
#include <sys/limits.h>
+// This value MUST be kept in sync with the corresponding value in
+// the android.net.Network#getNetworkHandle() implementation.
+static const uint32_t kHandleMagic = 0xcafed00d;
+static const uint32_t kHandleMagicSize = 32;
static int getnetidfromhandle(net_handle_t handle, unsigned *netid) {
static const uint32_t k32BitMask = 0xffffffff;
- // This value MUST be kept in sync with the corresponding value in
- // the android.net.Network#getNetworkHandle() implementation.
- static const uint32_t kHandleMagic = 0xcafed00d;
// Check for minimum acceptable version of the API in the low bits.
if (handle != NETWORK_UNSPECIFIED &&
@@ -41,6 +42,12 @@
return 1;
}
+static net_handle_t gethandlefromnetid(unsigned netid) {
+ if (netid == NETID_UNSET) {
+ return NETWORK_UNSPECIFIED;
+ }
+ return (((net_handle_t) netid) << kHandleMagicSize) | kHandleMagic;
+}
int android_setsocknetwork(net_handle_t network, int fd) {
unsigned netid;
@@ -72,6 +79,49 @@
return rval;
}
+int android_getprocnetwork(net_handle_t *network) {
+ if (network == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ unsigned netid = getNetworkForProcess();
+ *network = gethandlefromnetid(netid);
+ return 0;
+}
+
+int android_setprocdns(net_handle_t network) {
+ unsigned netid;
+ if (!getnetidfromhandle(network, &netid)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ int rval = setNetworkForResolv(netid);
+ if (rval < 0) {
+ errno = -rval;
+ rval = -1;
+ }
+ return rval;
+}
+
+int android_getprocdns(net_handle_t *network) {
+ if (network == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ unsigned netid;
+ int rval = getNetworkForDns(&netid);
+ if (rval < 0) {
+ errno = -rval;
+ return -1;
+ }
+
+ *network = gethandlefromnetid(netid);
+ return 0;
+}
+
int android_getaddrinfofornetwork(net_handle_t network,
const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index f9795c6..d36836c 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -25,6 +25,8 @@
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 657d5a3..3553c1f 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -128,6 +128,7 @@
srcs: [
"jni/android_net_NetworkUtils.cpp",
],
+ shared_libs: ["libandroid_net"],
apex_available: [
"//apex_available:platform",
"com.android.tethering",
@@ -140,6 +141,7 @@
srcs: [
"jni/onload.cpp",
],
+ shared_libs: ["libandroid"],
static_libs: ["libconnectivityframeworkutils"],
apex_available: [
"//apex_available:platform",
diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt
index ad44b27..0a9560a 100644
--- a/packages/Connectivity/framework/api/current.txt
+++ b/packages/Connectivity/framework/api/current.txt
@@ -68,6 +68,7 @@
method public boolean bindProcessToNetwork(@Nullable android.net.Network);
method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+ method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public android.net.Network getActiveNetworkForUid(int);
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
@@ -387,7 +388,9 @@
public class NetworkRequest implements android.os.Parcelable {
method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
method public int describeContents();
+ method @NonNull public int[] getCapabilities();
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+ method @NonNull public int[] getTransportTypes();
method public boolean hasCapability(int);
method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index b179c6d..f7c3965 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -11,6 +11,7 @@
method @Nullable public android.net.ProxyInfo getGlobalProxy();
method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
@@ -36,9 +37,11 @@
public final class NetworkAgentConfig implements android.os.Parcelable {
method @Nullable public String getSubscriberId();
+ method public boolean isBypassableVpn();
}
public static final class NetworkAgentConfig.Builder {
+ method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean);
method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
}
@@ -59,6 +62,7 @@
}
public class NetworkRequest implements android.os.Parcelable {
+ method @NonNull public int[] getUnwantedCapabilities();
method public boolean hasUnwantedCapability(int);
}
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index c19fcdd..9dcc391 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -212,6 +212,7 @@
public abstract class NetworkAgent {
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+ ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
method @Nullable public android.net.Network getNetwork();
method public void markConnected();
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
@@ -269,6 +270,7 @@
public final class NetworkCapabilities implements android.os.Parcelable {
method @NonNull public int[] getAdministratorUids();
+ method @Nullable public static String getCapabilityCarrierName(int);
method @Nullable public String getSsid();
method @NonNull public int[] getTransportTypes();
method public boolean isPrivateDnsBroken();
@@ -323,6 +325,19 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
}
+ public final class NetworkScore implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getLegacyInt();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
+ }
+
+ public static final class NetworkScore.Builder {
+ ctor public NetworkScore.Builder();
+ method @NonNull public android.net.NetworkScore build();
+ method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+ }
+
public final class OemNetworkPreferences implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index c5b1ff8..fd4d9db 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -19,6 +19,7 @@
#include <vector>
#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
#include <arpa/inet.h>
#include <linux/filter.h>
#include <linux/if_arp.h>
@@ -94,14 +95,21 @@
}
}
-static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
+ jlong netHandle)
{
- return (jboolean) !setNetworkForProcess(netId);
+ return (jboolean) !android_setprocnetwork(netHandle);
}
-static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz)
+static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
{
- return getNetworkForProcess();
+ net_handle_t network;
+ if (android_getprocnetwork(&network) != 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "android_getprocnetwork(): %s", strerror(errno));
+ return NETWORK_UNSPECIFIED;
+ }
+ return (jlong) network;
}
static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
@@ -115,11 +123,6 @@
return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd));
}
-static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
-{
- return (jboolean) !queryUserAccess(uid, netId);
-}
-
static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
{
if (env->GetArrayLength(addr) != len) {
@@ -255,11 +258,10 @@
// clang-format off
static const JNINativeMethod gNetworkUtilMethods[] = {
/* name, signature, funcPtr */
- { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
- { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
+ { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
+ { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
{ "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
- { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index f207830..20ff93f 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -1083,8 +1083,7 @@
*
* @return a {@link Network} object for the current default network for the
* given UID or {@code null} if no default network is currently active
- *
- * @hide
+ * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed
*/
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
@Nullable
@@ -3705,8 +3704,9 @@
private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
private static CallbackHandler sCallbackHandler;
- private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
- int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
+ private NetworkRequest sendRequestForNetwork(int asUid, NetworkCapabilities need,
+ NetworkCallback callback, int timeoutMs, NetworkRequest.Type reqType, int legacyType,
+ CallbackHandler handler) {
printStackTrace();
checkCallbackNotNull(callback);
if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) {
@@ -3731,8 +3731,8 @@
getAttributionTag());
} else {
request = mService.requestNetwork(
- need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType,
- callbackFlags, callingPackageName, getAttributionTag());
+ asUid, need, reqType.ordinal(), messenger, timeoutMs, binder,
+ legacyType, callbackFlags, callingPackageName, getAttributionTag());
}
if (request != null) {
sCallbacks.put(request, callback);
@@ -3747,6 +3747,12 @@
return request;
}
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
+ int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
+ return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType,
+ legacyType, handler);
+ }
+
/**
* Helper function to request a network with a particular legacy type.
*
@@ -4232,8 +4238,40 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
@NonNull Handler handler) {
+ registerDefaultNetworkCallbackAsUid(Process.INVALID_UID, networkCallback, handler);
+ }
+
+ /**
+ * Registers to receive notifications about changes in the default network for the specified
+ * UID. This may be a physical network or a virtual network, such as a VPN that applies to the
+ * UID. The callbacks will continue to be called until either the application exits or
+ * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+ *
+ * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
+ * number of outstanding requests to 100 per app (identified by their UID), shared with
+ * all variants of this method, of {@link #requestNetwork} as well as
+ * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}.
+ * Requesting a network with this method will count toward this limit. If this limit is
+ * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources,
+ * make sure to unregister the callbacks with
+ * {@link #unregisterNetworkCallback(NetworkCallback)}.
+ *
+ * @param uid the UID for which to track default network changes.
+ * @param networkCallback The {@link NetworkCallback} that the system will call as the
+ * UID's default network changes.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ * @throws RuntimeException if the app already has too many callbacks registered.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @SuppressLint({"ExecutorRegistration", "PairedRegistration"})
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void registerDefaultNetworkCallbackAsUid(int uid,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
CallbackHandler cbHandler = new CallbackHandler(handler);
- sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+ sendRequestForNetwork(uid, null /* need */, networkCallback, 0 /* timeoutMs */,
TRACK_DEFAULT, TYPE_NONE, cbHandler);
}
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index 3300fa8..0826922 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -142,7 +142,7 @@
in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config,
in int factorySerialNumber);
- NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType,
+ NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy,
int callbackFlags, String callingPackageName, String callingAttributionTag);
diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java
index 0741414..41fad63 100644
--- a/packages/Connectivity/framework/src/android/net/Network.java
+++ b/packages/Connectivity/framework/src/android/net/Network.java
@@ -27,7 +27,6 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
@@ -526,11 +525,4 @@
public String toString() {
return Integer.toString(netId);
}
-
- /** @hide */
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(NetworkProto.NET_ID, netId);
- proto.end(token);
- }
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 1ff0140..b3d9616 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -389,7 +389,6 @@
* @param score the initial score of this network. Update with sendNetworkScore.
* @param config an immutable {@link NetworkAgentConfig} for this agent.
* @param provider the {@link NetworkProvider} managing this agent.
- * @hide TODO : unhide when impl is complete
*/
public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
@NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
index fb6fcc1..3f058d8 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
@@ -64,6 +64,16 @@
}
/**
+ * @return whether this VPN connection can be bypassed by the apps.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public boolean isBypassableVpn() {
+ return allowBypass;
+ }
+
+ /**
* Set if the user desires to use this network even if it is unvalidated. This field has meaning
* only if {@link explicitlySelected} is true. If it is, this field must also be set to the
* appropriate value based on previous user choice.
@@ -382,6 +392,19 @@
}
/**
+ * Sets whether the apps can bypass the VPN connection.
+ *
+ * @return this builder, to facilitate chaining.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
+ public Builder setBypassableVpn(boolean allowBypass) {
+ mConfig.allowBypass = allowBypass;
+ return this;
+ }
+
+ /**
* Returns the constructed {@link NetworkAgentConfig} object.
*/
@NonNull
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 881fa8c..f50b018 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -35,7 +35,6 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Range;
-import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
@@ -538,43 +537,6 @@
| (1 << NET_CAPABILITY_NOT_VPN);
/**
- * Capabilities that suggest that a network is restricted.
- * {@see #maybeMarkCapabilitiesRestricted}, {@see #FORCE_RESTRICTED_CAPABILITIES}
- */
- @VisibleForTesting
- /* package */ static final long RESTRICTED_CAPABILITIES =
- (1 << NET_CAPABILITY_CBS)
- | (1 << NET_CAPABILITY_DUN)
- | (1 << NET_CAPABILITY_EIMS)
- | (1 << NET_CAPABILITY_FOTA)
- | (1 << NET_CAPABILITY_IA)
- | (1 << NET_CAPABILITY_IMS)
- | (1 << NET_CAPABILITY_MCX)
- | (1 << NET_CAPABILITY_RCS)
- | (1 << NET_CAPABILITY_VEHICLE_INTERNAL)
- | (1 << NET_CAPABILITY_XCAP)
- | (1 << NET_CAPABILITY_ENTERPRISE);
-
- /**
- * Capabilities that force network to be restricted.
- * {@see #maybeMarkCapabilitiesRestricted}.
- */
- private static final long FORCE_RESTRICTED_CAPABILITIES =
- (1 << NET_CAPABILITY_OEM_PAID)
- | (1 << NET_CAPABILITY_OEM_PRIVATE);
-
- /**
- * Capabilities that suggest that a network is unrestricted.
- * {@see #maybeMarkCapabilitiesRestricted}.
- */
- @VisibleForTesting
- /* package */ static final long UNRESTRICTED_CAPABILITIES =
- (1 << NET_CAPABILITY_INTERNET)
- | (1 << NET_CAPABILITY_MMS)
- | (1 << NET_CAPABILITY_SUPL)
- | (1 << NET_CAPABILITY_WIFI_P2P);
-
- /**
* Capabilities that are managed by ConnectivityService.
*/
private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
@@ -749,6 +711,23 @@
return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
}
+ /**
+ * Get the name of the given capability that carriers use.
+ * If the capability does not have a carrier-name, returns null.
+ *
+ * @param capability The capability to get the carrier-name of.
+ * @return The carrier-name of the capability, or null if it doesn't exist.
+ * @hide
+ */
+ @SystemApi
+ public static @Nullable String getCapabilityCarrierName(@NetCapability int capability) {
+ if (capability == NET_CAPABILITY_ENTERPRISE) {
+ return capabilityNameOf(capability);
+ } else {
+ return null;
+ }
+ }
+
private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
final long unwantedCaps =
@@ -811,37 +790,12 @@
}
/**
- * Deduces that all the capabilities it provides are typically provided by restricted networks
- * or not.
- *
- * @return {@code true} if the network should be restricted.
- * @hide
- */
- public boolean deduceRestrictedCapability() {
- // Check if we have any capability that forces the network to be restricted.
- final boolean forceRestrictedCapability =
- (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0;
-
- // Verify there aren't any unrestricted capabilities. If there are we say
- // the whole thing is unrestricted unless it is forced to be restricted.
- final boolean hasUnrestrictedCapabilities =
- (mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0;
-
- // Must have at least some restricted capabilities.
- final boolean hasRestrictedCapabilities =
- (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0;
-
- return forceRestrictedCapability
- || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities);
- }
-
- /**
- * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted.
+ * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if inferring the network is restricted.
*
* @hide
*/
public void maybeMarkCapabilitiesRestricted() {
- if (deduceRestrictedCapability()) {
+ if (NetworkCapabilitiesUtils.inferRestrictedCapability(this)) {
removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
}
}
@@ -2087,34 +2041,6 @@
}
}
- /** @hide */
- public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- for (int transport : getTransportTypes()) {
- proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport);
- }
-
- for (int capability : getCapabilities()) {
- proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability);
- }
-
- proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps);
- proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps);
-
- if (mNetworkSpecifier != null) {
- proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString());
- }
- if (mTransportInfo != null) {
- // TODO b/120653863: write transport-specific info to proto?
- }
-
- proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength());
- proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength);
-
- proto.end(token);
- }
-
/**
* @hide
*/
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index bcbc04f7..5313f08 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -47,7 +47,6 @@
import android.os.Process;
import android.text.TextUtils;
import android.util.Range;
-import android.util.proto.ProtoOutputStream;
import java.util.Arrays;
import java.util.List;
@@ -675,18 +674,6 @@
}
}
- /** @hide */
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type));
- proto.write(NetworkRequestProto.REQUEST_ID, requestId);
- proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType);
- networkCapabilities.dumpDebug(proto, NetworkRequestProto.NETWORK_CAPABILITIES);
-
- proto.end(token);
- }
-
public boolean equals(@Nullable Object obj) {
if (obj instanceof NetworkRequest == false) return false;
NetworkRequest that = (NetworkRequest)obj;
@@ -699,4 +686,43 @@
public int hashCode() {
return Objects.hash(requestId, legacyType, networkCapabilities, type);
}
+
+ /**
+ * Gets all the capabilities set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of capability values for this instance.
+ */
+ @NonNull
+ public @NetCapability int[] getCapabilities() {
+ // No need to make a defensive copy here as NC#getCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getCapabilities();
+ }
+
+ /**
+ * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of unwanted capability values for this instance.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @NetCapability int[] getUnwantedCapabilities() {
+ // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getUnwantedCapabilities();
+ }
+
+ /**
+ * Gets all the transports set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of transport type values for this instance.
+ */
+ @NonNull
+ public @Transport int[] getTransportTypes() {
+ // No need to make a defensive copy here as NC#getTransportTypes() already returns
+ // a new array.
+ return networkCapabilities.getTransportTypes();
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java
index eadcb2d..6584993 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkScore.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -29,7 +30,7 @@
* network is considered for a particular use.
* @hide
*/
-// TODO : @SystemApi when the implementation is complete
+@SystemApi
public final class NetworkScore implements Parcelable {
// This will be removed soon. Do *NOT* depend on it for any new code that is not part of
// a migration.
@@ -62,6 +63,8 @@
/**
* @return whether this score has a particular policy.
+ *
+ * @hide
*/
@VisibleForTesting
public boolean hasPolicy(final int policy) {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index c4bebc0..f524859 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.net.ConnectivityManager.NETID_UNSET;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.system.ErrnoException;
@@ -55,6 +57,8 @@
*/
public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
+ private static native boolean bindProcessToNetworkHandle(long netHandle);
+
/**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
@@ -63,13 +67,20 @@
* is by design so an application doesn't accidentally use sockets it thinks are still bound to
* a particular {@code Network}. Passing NETID_UNSET clears the binding.
*/
- public native static boolean bindProcessToNetwork(int netId);
+ public static boolean bindProcessToNetwork(int netId) {
+ return bindProcessToNetworkHandle(new Network(netId).getNetworkHandle());
+ }
+
+ private static native long getBoundNetworkHandleForProcess();
/**
* Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
* {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
*/
- public native static int getBoundNetworkForProcess();
+ public static int getBoundNetworkForProcess() {
+ final long netHandle = getBoundNetworkHandleForProcess();
+ return netHandle == 0L ? NETID_UNSET : Network.fromNetworkHandle(netHandle).getNetId();
+ }
/**
* Binds host resolutions performed by this process to the network designated by {@code netId}.
@@ -92,7 +103,10 @@
* Determine if {@code uid} can access network designated by {@code netId}.
* @return {@code true} if {@code uid} can access network, {@code false} otherwise.
*/
- public native static boolean queryUserAccess(int uid, int netId);
+ public static boolean queryUserAccess(int uid, int netId) {
+ // TODO (b/183485986): remove this method
+ return false;
+ }
/**
* DNS resolver series jni method.
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index 1330e71..37dd9ff 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -51,22 +51,33 @@
java_library {
name: "service-connectivity-pre-jarjar",
+ sdk_version: "system_server_current",
srcs: [
- ":framework-connectivity-shared-srcs",
":connectivity-service-srcs",
+ ":framework-connectivity-shared-srcs",
+ ":services-connectivity-shared-srcs",
+ // TODO: move to net-utils-device-common, enable shrink optimization to avoid extra classes
+ ":net-module-utils-srcs",
],
libs: [
- "android.net.ipsec.ike",
- "services.core",
- "services.net",
+ // TODO (b/183097033) remove once system_server_current includes core_current
+ "stable.core.platform.api.stubs",
+ "android_system_server_stubs_current",
+ "framework-annotations-lib",
+ "framework-connectivity.impl",
+ "framework-tethering.stubs.module_lib",
+ "framework-wifi.stubs.module_lib",
"unsupportedappusage",
"ServiceConnectivityResources",
],
static_libs: [
+ "dnsresolver_aidl_interface-V7-java",
"modules-utils-os",
"net-utils-device-common",
"net-utils-framework-common",
"netd-client",
+ "netlink-client",
+ "networkstack-client",
"PlatformProperties",
"service-connectivity-protos",
],
@@ -78,6 +89,7 @@
java_library {
name: "service-connectivity-protos",
+ sdk_version: "system_current",
proto: {
type: "nano",
},
@@ -93,6 +105,7 @@
java_library {
name: "service-connectivity",
+ sdk_version: "system_server_current",
installable: true,
static_libs: [
"service-connectivity-pre-jarjar",
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 7cc5994..c4c60ea 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -151,20 +151,14 @@
}
@Override
- public void onInitialize(boolean allowed, ProviderProperties properties, String packageName,
- String attributionTag) {
-
- }
+ public void onInitialize(boolean allowed, ProviderProperties properties,
+ String attributionTag) {}
@Override
- public void onSetAllowed(boolean allowed) {
-
- }
+ public void onSetAllowed(boolean allowed) {}
@Override
- public void onSetProperties(ProviderProperties properties) {
-
- }
+ public void onSetProperties(ProviderProperties properties) {}
@Override
public void onReportLocation(Location location) {
@@ -177,9 +171,7 @@
}
@Override
- public void onFlushComplete() {
-
- }
+ public void onFlushComplete() {}
public Location getNextLocation(long timeoutMs) throws InterruptedException {
return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d801f1b..7186ec5 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1467,7 +1467,7 @@
<string name="data_connection_5g_plus" translatable="false">5G+</string>
<!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] -->
- <string name="data_connection_carrier_wifi">CWF</string>
+ <string name="data_connection_carrier_wifi">W+</string>
<!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] -->
<string name="cell_data_off_content_description">Mobile data off</string>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 068efac..3877b1e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -749,7 +749,8 @@
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
Settings.Secure.SUPPRESS_DOZE,
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT);
+ Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+ Settings.Secure.TRANSFORM_ENABLED);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2b4fef0..cff8ad1 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -39,12 +39,16 @@
<uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
<!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
@@ -237,6 +241,9 @@
<!-- Permission needed to run keyguard manager tests in CTS -->
<uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
+ <!-- Permission needed to set/clear/verify lockscreen credentials in CTS tests -->
+ <uses-permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" />
+
<!-- Permission needed to test wallpaper component -->
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3904201..4135bbe 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -62,6 +62,8 @@
<!-- Networking and telephony -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
diff --git a/packages/SystemUI/res/drawable/people_space_messages_count_background.xml b/packages/SystemUI/res/drawable/people_space_messages_count_background.xml
new file mode 100644
index 0000000..0fc112e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/people_space_messages_count_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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" >
+ <solid android:color="#9ED582" />
+ <corners android:radius="@dimen/people_space_messages_count_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index e4e4cd8..db1d46d 100644
--- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -98,8 +98,8 @@
android:orientation="horizontal"
android:paddingTop="4dp"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:clipToOutline="true">
<TextView
android:id="@+id/name"
android:gravity="center_vertical"
@@ -112,7 +112,21 @@
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
-
+ <TextView
+ android:id="@+id/messages_count"
+ android:gravity="end"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:background="@drawable/people_space_messages_count_background"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
<ImageView
android:id="@+id/predefined_icon"
android:gravity="end|center_vertical"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ff4e8e0..2393b74 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1353,6 +1353,7 @@
<dimen name="people_space_widget_radius">28dp</dimen>
<dimen name="people_space_image_radius">20dp</dimen>
+ <dimen name="people_space_messages_count_radius">12dp</dimen>
<dimen name="people_space_widget_background_padding">6dp</dimen>
<dimen name="required_width_for_medium">146dp</dimen>
<dimen name="required_width_for_large">138dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 691d111..94bf86a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2865,6 +2865,8 @@
<string name="empty_status">Let’s chat tonight!</string>
<!-- Default text for missed call notifications on their Conversation widget [CHAR LIMIT=20] -->
<string name="missed_call">Missed call</string>
+ <!-- Text when a Notification may have more messages than the number indicated [CHAR LIMIT=5] -->
+ <string name="messages_count_overflow_indicator"><xliff:g id="number" example="7">%d</xliff:g>+</string>
<!-- Description text for adding a Conversation widget [CHAR LIMIT=100] -->
<string name="people_tile_description">See recent messages, missed calls, and status updates</string>
<!-- Title text displayed for the Conversation widget [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index f6b03c1..e4f6e131 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -24,41 +24,14 @@
import android.view.View;
import android.widget.TextView;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import java.util.Locale;
public class CarrierText extends TextView {
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final String TAG = "CarrierText";
+ private final boolean mShowMissingSim;
- private static CharSequence mSeparator;
-
- private boolean mShowMissingSim;
-
- private boolean mShowAirplaneMode;
- private boolean mShouldMarquee;
-
- private CarrierTextController mCarrierTextController;
-
- private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
- new CarrierTextController.CarrierTextCallback() {
- @Override
- public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
- setText(info.carrierText);
- }
-
- @Override
- public void startedGoingToSleep() {
- setSelected(false);
- }
-
- @Override
- public void finishedWakingUp() {
- setSelected(true);
- }
- };
+ private final boolean mShowAirplaneMode;
public CarrierText(Context context) {
this(context, null);
@@ -78,30 +51,6 @@
}
setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
}
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSeparator = getResources().getString(
- com.android.internal.R.string.kg_text_message_separator);
- mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
- mShowMissingSim);
- mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
- setSelected(mShouldMarquee); // Allow marquee to work.
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mCarrierTextController.setListening(mCarrierTextCallback);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mCarrierTextController.setListening(null);
- }
-
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -113,7 +62,15 @@
}
}
- private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
+ public boolean getShowAirplaneMode() {
+ return mShowAirplaneMode;
+ }
+
+ public boolean getShowMissingSim() {
+ return mShowMissingSim;
+ }
+
+ private static class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
private final Locale mLocale;
private final boolean mAllCaps;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index d52a251..997c527 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,680 +16,60 @@
package com.android.keyguard;
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
-import static android.telephony.PhoneStateListener.LISTEN_NONE;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.WirelessUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
+import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/**
- * Controller that generates text including the carrier names and/or the status of all the SIM
- * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
- * separated by a given separator {@link CharSequence}.
+ * Controller for {@link CarrierText}.
*/
-public class CarrierTextController {
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final String TAG = "CarrierTextController";
-
- private final boolean mIsEmergencyCallCapable;
- private final Handler mMainHandler;
- private final Handler mBgHandler;
- private boolean mTelephonyCapable;
- private boolean mShowMissingSim;
- private boolean mShowAirplaneMode;
- private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
- @VisibleForTesting
- protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private WifiManager mWifiManager;
- private boolean[] mSimErrorState;
- private final int mSimSlotsNumber;
- @Nullable // Check for nullability before dispatching
- private CarrierTextCallback mCarrierTextCallback;
- private Context mContext;
- private CharSequence mSeparator;
- private WakefulnessLifecycle mWakefulnessLifecycle;
- private final WakefulnessLifecycle.Observer mWakefulnessObserver =
- new WakefulnessLifecycle.Observer() {
+public class CarrierTextController extends ViewController<CarrierText> {
+ private final CarrierTextManager mCarrierTextManager;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final CarrierTextManager.CarrierTextCallback mCarrierTextCallback =
+ new CarrierTextManager.CarrierTextCallback() {
@Override
- public void onFinishedWakingUp() {
- final CarrierTextCallback callback = mCarrierTextCallback;
- if (callback != null) callback.finishedWakingUp();
+ public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
+ mView.setText(info.carrierText);
}
@Override
- public void onStartedGoingToSleep() {
- final CarrierTextCallback callback = mCarrierTextCallback;
- if (callback != null) callback.startedGoingToSleep();
+ public void startedGoingToSleep() {
+ mView.setSelected(false);
+ }
+
+ @Override
+ public void finishedWakingUp() {
+ mView.setSelected(true);
}
};
- @VisibleForTesting
- protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onRefreshCarrierInfo() {
- if (DEBUG) {
- Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
- + Boolean.toString(mTelephonyCapable));
- }
- updateCarrierText();
- }
+ @Inject
+ public CarrierTextController(CarrierText view,
+ CarrierTextManager.Builder carrierTextManagerBuilder,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ super(view);
- @Override
- public void onTelephonyCapable(boolean capable) {
- if (DEBUG) {
- Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
- + Boolean.toString(capable));
- }
- mTelephonyCapable = capable;
- updateCarrierText();
- }
-
- public void onSimStateChanged(int subId, int slotId, int simState) {
- if (slotId < 0 || slotId >= mSimSlotsNumber) {
- Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
- + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
- return;
- }
-
- if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
- if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
- mSimErrorState[slotId] = true;
- updateCarrierText();
- } else if (mSimErrorState[slotId]) {
- mSimErrorState[slotId] = false;
- updateCarrierText();
- }
- }
- };
-
- private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onActiveDataSubscriptionIdChanged(int subId) {
- mActiveMobileDataSubscription = subId;
- if (mNetworkSupported.get() && mCarrierTextCallback != null) {
- updateCarrierText();
- }
- }
- };
-
- /**
- * The status of this lock screen. Primarily used for widgets on LockScreen.
- */
- private enum StatusMode {
- Normal, // Normal case (sim card present, it's not locked)
- NetworkLocked, // SIM card is 'network locked'.
- SimMissing, // SIM card is missing.
- SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
- SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
- SimLocked, // SIM card is currently locked
- SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
- SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
- SimIoError, // SIM card is faulty
- SimUnknown // SIM card is unknown
+ mCarrierTextManager = carrierTextManagerBuilder
+ .setShowAirplaneMode(mView.getShowAirplaneMode())
+ .setShowMissingSim(mView.getShowMissingSim())
+ .build();
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}
- /**
- * Controller that provides updates on text with carriers names or SIM status.
- * Used by {@link CarrierText}.
- *
- * @param separator Separator between different parts of the text
- */
- public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
- boolean showMissingSim) {
- mContext = context;
- mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable();
-
- mShowAirplaneMode = showAirplaneMode;
- mShowMissingSim = showMissingSim;
-
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- mSeparator = separator;
- mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
- mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
- mSimErrorState = new boolean[mSimSlotsNumber];
- mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
- mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mBgHandler.post(() -> {
- boolean supported =
- mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
- if (supported && mNetworkSupported.compareAndSet(false, supported)) {
- // This will set/remove the listeners appropriately. Note that it will never double
- // add the listeners.
- handleSetListening(mCarrierTextCallback);
- }
- });
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
}
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ @Override
+ protected void onViewAttached() {
+ mCarrierTextManager.setListening(mCarrierTextCallback);
}
- /**
- * Checks if there are faulty cards. Adds the text depending on the slot of the card
- *
- * @param text: current carrier text based on the sim state
- * @param carrierNames names order by subscription order
- * @param subOrderBySlot array containing the sub index for each slot ID
- * @param noSims: whether a valid sim card is inserted
- * @return text
- */
- private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
- CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
- final CharSequence carrier = "";
- CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
- TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
- // mSimErrorState has the state of each sim indexed by slotID.
- for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
- if (!mSimErrorState[index]) {
- continue;
- }
- // In the case when no sim cards are detected but a faulty card is inserted
- // overwrite the text and only show "Invalid card"
- if (noSims) {
- return concatenate(carrierTextForSimIOError,
- getContext().getText(
- com.android.internal.R.string.emergency_calls_only),
- mSeparator);
- } else if (subOrderBySlot[index] != -1) {
- int subIndex = subOrderBySlot[index];
- // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
- carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
- carrierNames[subIndex],
- mSeparator);
- } else {
- // concatenate "Invalid card" when faulty card is inserted in other slot
- text = concatenate(text, carrierTextForSimIOError, mSeparator);
- }
-
- }
- return text;
- }
-
- /**
- * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
- * (assumed false to start). In that case, the following happens:
- * <ul>
- * <li> If there was a registered callback, and the network is supported, it will register
- * listeners.
- * <li> If there was not a registered callback, it will try to remove unregistered listeners
- * which is a no-op
- * </ul>
- *
- * This call will always be processed in a background thread.
- */
- private void handleSetListening(CarrierTextCallback callback) {
- TelephonyManager telephonyManager = getTelephonyManager();
- if (callback != null) {
- mCarrierTextCallback = callback;
- if (mNetworkSupported.get()) {
- // Keyguard update monitor expects callbacks from main thread
- mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
- mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
- telephonyManager.listen(mPhoneStateListener,
- LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
- } else {
- // Don't listen and clear out the text when the device isn't a phone.
- mMainHandler.post(() -> callback.updateCarrierInfo(
- new CarrierTextCallbackInfo("", null, false, null)
- ));
- }
- } else {
- mCarrierTextCallback = null;
- mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
- mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
- telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
- }
- }
-
- /**
- * Sets the listening status of this controller. If the callback is null, it is set to
- * not listening.
- *
- * @param callback Callback to provide text updates
- */
- public void setListening(CarrierTextCallback callback) {
- mBgHandler.post(() -> handleSetListening(callback));
- }
-
- protected List<SubscriptionInfo> getSubscriptionInfo() {
- return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
- }
-
- protected void updateCarrierText() {
- boolean allSimsMissing = true;
- boolean anySimReadyAndInService = false;
- CharSequence displayText = null;
- List<SubscriptionInfo> subs = getSubscriptionInfo();
-
- final int numSubs = subs.size();
- final int[] subsIds = new int[numSubs];
- // This array will contain in position i, the index of subscription in slot ID i.
- // -1 if no subscription in that slot
- final int[] subOrderBySlot = new int[mSimSlotsNumber];
- for (int i = 0; i < mSimSlotsNumber; i++) {
- subOrderBySlot[i] = -1;
- }
- final CharSequence[] carrierNames = new CharSequence[numSubs];
- if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
-
- for (int i = 0; i < numSubs; i++) {
- int subId = subs.get(i).getSubscriptionId();
- carrierNames[i] = "";
- subsIds[i] = subId;
- subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
- int simState = mKeyguardUpdateMonitor.getSimState(subId);
- CharSequence carrierName = subs.get(i).getCarrierName();
- CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
- if (DEBUG) {
- Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
- }
- if (carrierTextForSimState != null) {
- allSimsMissing = false;
- carrierNames[i] = carrierTextForSimState;
- }
- if (simState == TelephonyManager.SIM_STATE_READY) {
- ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
- if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
- // hack for WFC (IWLAN) not turning off immediately once
- // Wi-Fi is disassociated or disabled
- if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
- || (mWifiManager.isWifiEnabled()
- && mWifiManager.getConnectionInfo() != null
- && mWifiManager.getConnectionInfo().getBSSID() != null)) {
- if (DEBUG) {
- Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
- }
- anySimReadyAndInService = true;
- }
- }
- }
- }
- // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
- // This condition will also be true always when numSubs == 0
- if (allSimsMissing && !anySimReadyAndInService) {
- if (numSubs != 0) {
- // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
- // This depends on mPlmn containing the text "Emergency calls only" when the radio
- // has some connectivity. Otherwise, it should be null or empty and just show
- // "No SIM card"
- // Grab the first subscripton, because they all should contain the emergency text,
- // described above.
- displayText = makeCarrierStringOnEmergencyCapable(
- getMissingSimMessage(), subs.get(0).getCarrierName());
- } else {
- // We don't have a SubscriptionInfo to get the emergency calls only from.
- // Grab it from the old sticky broadcast if possible instead. We can use it
- // here because no subscriptions are active, so we don't have
- // to worry about MSIM clashing.
- CharSequence text =
- getContext().getText(com.android.internal.R.string.emergency_calls_only);
- Intent i = getContext().registerReceiver(null,
- new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
- if (i != null) {
- String spn = "";
- String plmn = "";
- if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
- spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
- }
- if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
- plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
- }
- if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
- if (Objects.equals(plmn, spn)) {
- text = plmn;
- } else {
- text = concatenate(plmn, spn, mSeparator);
- }
- }
- displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
- }
- }
-
- if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
-
- displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
- allSimsMissing);
-
- boolean airplaneMode = false;
- // APM (airplane mode) != no carrier state. There are carrier services
- // (e.g. WFC = Wi-Fi calling) which may operate in APM.
- if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
- displayText = getAirplaneModeMessage();
- airplaneMode = true;
- }
-
- final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
- displayText,
- carrierNames,
- !allSimsMissing,
- subsIds,
- airplaneMode);
- postToCallback(info);
- }
-
- @VisibleForTesting
- protected void postToCallback(CarrierTextCallbackInfo info) {
- final CarrierTextCallback callback = mCarrierTextCallback;
- if (callback != null) {
- mMainHandler.post(() -> callback.updateCarrierInfo(info));
- }
- }
-
- private Context getContext() {
- return mContext;
- }
-
- private String getMissingSimMessage() {
- return mShowMissingSim && mTelephonyCapable
- ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
- }
-
- private String getAirplaneModeMessage() {
- return mShowAirplaneMode
- ? getContext().getString(R.string.airplane_mode) : "";
- }
-
- /**
- * Top-level function for creating carrier text. Makes text based on simState, PLMN
- * and SPN as well as device capabilities, such as being emergency call capable.
- *
- * @return Carrier text if not in missing state, null otherwise.
- */
- private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
- CharSequence carrierText = null;
- CarrierTextController.StatusMode status = getStatusForIccState(simState);
- switch (status) {
- case Normal:
- carrierText = text;
- break;
-
- case SimNotReady:
- // Null is reserved for denoting missing, in this case we have nothing to display.
- carrierText = ""; // nothing to display yet.
- break;
-
- case NetworkLocked:
- carrierText = makeCarrierStringOnEmergencyCapable(
- mContext.getText(R.string.keyguard_network_locked_message), text);
- break;
-
- case SimMissing:
- carrierText = null;
- break;
-
- case SimPermDisabled:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(
- R.string.keyguard_permanent_disabled_sim_message_short),
- text);
- break;
-
- case SimMissingLocked:
- carrierText = null;
- break;
-
- case SimLocked:
- carrierText = makeCarrierStringOnLocked(
- getContext().getText(R.string.keyguard_sim_locked_message),
- text);
- break;
-
- case SimPukLocked:
- carrierText = makeCarrierStringOnLocked(
- getContext().getText(R.string.keyguard_sim_puk_locked_message),
- text);
- break;
- case SimIoError:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(R.string.keyguard_sim_error_message_short),
- text);
- break;
- case SimUnknown:
- carrierText = null;
- break;
- }
-
- return carrierText;
- }
-
- /*
- * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
- */
- private CharSequence makeCarrierStringOnEmergencyCapable(
- CharSequence simMessage, CharSequence emergencyCallMessage) {
- if (mIsEmergencyCallCapable) {
- return concatenate(simMessage, emergencyCallMessage, mSeparator);
- }
- return simMessage;
- }
-
- /*
- * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
- * DSDS
- */
- private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
- CharSequence carrierName) {
- final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
- final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
- if (simMessageValid && carrierNameValid) {
- return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
- carrierName, simMessage);
- } else if (simMessageValid) {
- return simMessage;
- } else if (carrierNameValid) {
- return carrierName;
- } else {
- return "";
- }
- }
-
- /**
- * Determine the current status of the lock screen given the SIM state and other stuff.
- */
- private CarrierTextController.StatusMode getStatusForIccState(int simState) {
- final boolean missingAndNotProvisioned =
- !mKeyguardUpdateMonitor.isDeviceProvisioned()
- && (simState == TelephonyManager.SIM_STATE_ABSENT
- || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
-
- // Assume we're NETWORK_LOCKED if not provisioned
- simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
- switch (simState) {
- case TelephonyManager.SIM_STATE_ABSENT:
- return CarrierTextController.StatusMode.SimMissing;
- case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
- return CarrierTextController.StatusMode.SimMissingLocked;
- case TelephonyManager.SIM_STATE_NOT_READY:
- return CarrierTextController.StatusMode.SimNotReady;
- case TelephonyManager.SIM_STATE_PIN_REQUIRED:
- return CarrierTextController.StatusMode.SimLocked;
- case TelephonyManager.SIM_STATE_PUK_REQUIRED:
- return CarrierTextController.StatusMode.SimPukLocked;
- case TelephonyManager.SIM_STATE_READY:
- return CarrierTextController.StatusMode.Normal;
- case TelephonyManager.SIM_STATE_PERM_DISABLED:
- return CarrierTextController.StatusMode.SimPermDisabled;
- case TelephonyManager.SIM_STATE_UNKNOWN:
- return CarrierTextController.StatusMode.SimUnknown;
- case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
- return CarrierTextController.StatusMode.SimIoError;
- }
- return CarrierTextController.StatusMode.SimUnknown;
- }
-
- private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
- CharSequence separator) {
- final boolean plmnValid = !TextUtils.isEmpty(plmn);
- final boolean spnValid = !TextUtils.isEmpty(spn);
- if (plmnValid && spnValid) {
- return new StringBuilder().append(plmn).append(separator).append(spn).toString();
- } else if (plmnValid) {
- return plmn;
- } else if (spnValid) {
- return spn;
- } else {
- return "";
- }
- }
-
- /**
- * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
- * separator added so there are no extra separators that are not needed.
- */
- private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
- int length = sequences.length;
- if (length == 0) return "";
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < length; i++) {
- if (!TextUtils.isEmpty(sequences[i])) {
- if (!TextUtils.isEmpty(sb)) {
- sb.append(separator);
- }
- sb.append(sequences[i]);
- }
- }
- return sb.toString();
- }
-
- private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
- if (!TextUtils.isEmpty(string)) {
- list.add(string);
- }
- return list;
- }
-
- private CharSequence getCarrierHelpTextForSimState(int simState,
- String plmn, String spn) {
- int carrierHelpTextId = 0;
- CarrierTextController.StatusMode status = getStatusForIccState(simState);
- switch (status) {
- case NetworkLocked:
- carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
- break;
-
- case SimMissing:
- carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
- break;
-
- case SimPermDisabled:
- carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
- break;
-
- case SimMissingLocked:
- carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
- break;
-
- case Normal:
- case SimLocked:
- case SimPukLocked:
- break;
- }
-
- return mContext.getText(carrierHelpTextId);
- }
-
- public static class Builder {
- private final Context mContext;
- private final String mSeparator;
- private boolean mShowAirplaneMode;
- private boolean mShowMissingSim;
-
- @Inject
- public Builder(Context context, @Main Resources resources) {
- mContext = context;
- mSeparator = resources.getString(
- com.android.internal.R.string.kg_text_message_separator);
- }
-
-
- public Builder setShowAirplaneMode(boolean showAirplaneMode) {
- mShowAirplaneMode = showAirplaneMode;
- return this;
- }
-
- public Builder setShowMissingSim(boolean showMissingSim) {
- mShowMissingSim = showMissingSim;
- return this;
- }
-
- public CarrierTextController build() {
- return new CarrierTextController(
- mContext, mSeparator, mShowAirplaneMode, mShowMissingSim);
- }
- }
- /**
- * Data structure for passing information to CarrierTextController subscribers
- */
- public static final class CarrierTextCallbackInfo {
- public final CharSequence carrierText;
- public final CharSequence[] listOfCarriers;
- public final boolean anySimReady;
- public final int[] subscriptionIds;
- public boolean airplaneMode;
-
- @VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds) {
- this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
- }
-
- @VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
- this.carrierText = carrierText;
- this.listOfCarriers = listOfCarriers;
- this.anySimReady = anySimReady;
- this.subscriptionIds = subscriptionIds;
- this.airplaneMode = airplaneMode;
- }
- }
-
- /**
- * Callback to communicate to Views
- */
- public interface CarrierTextCallback {
- /**
- * Provides updated carrier information.
- */
- default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
-
- /**
- * Notifies the View that the device is going to sleep
- */
- default void startedGoingToSleep() {};
-
- /**
- * Notifies the View that the device finished waking up
- */
- default void finishedWakingUp() {};
+ @Override
+ protected void onViewDetached() {
+ mCarrierTextManager.setListening(null);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
new file mode 100644
index 0000000..bb1d972
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.wifi.WifiManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.telephony.TelephonyListenerManager;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * Controller that generates text including the carrier names and/or the status of all the SIM
+ * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
+ * separated by a given separator {@link CharSequence}.
+ */
+public class CarrierTextManager {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "CarrierTextController";
+
+ private final boolean mIsEmergencyCallCapable;
+ private final Executor mMainExecutor;
+ private final Executor mBgExecutor;
+ private boolean mTelephonyCapable;
+ private final boolean mShowMissingSim;
+ private final boolean mShowAirplaneMode;
+ private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
+ @VisibleForTesting
+ protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final WifiManager mWifiManager;
+ private final boolean[] mSimErrorState;
+ private final int mSimSlotsNumber;
+ @Nullable // Check for nullability before dispatching
+ private CarrierTextCallback mCarrierTextCallback;
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+ private final CharSequence mSeparator;
+ private final TelephonyListenerManager mTelephonyListenerManager;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+ new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onFinishedWakingUp() {
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) callback.finishedWakingUp();
+ }
+
+ @Override
+ public void onStartedGoingToSleep() {
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) callback.startedGoingToSleep();
+ }
+ };
+
+ @VisibleForTesting
+ protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ if (DEBUG) {
+ Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
+ + Boolean.toString(mTelephonyCapable));
+ }
+ updateCarrierText();
+ }
+
+ @Override
+ public void onTelephonyCapable(boolean capable) {
+ if (DEBUG) {
+ Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
+ + Boolean.toString(capable));
+ }
+ mTelephonyCapable = capable;
+ updateCarrierText();
+ }
+
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ if (slotId < 0 || slotId >= mSimSlotsNumber) {
+ Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
+ + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+ if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
+ mSimErrorState[slotId] = true;
+ updateCarrierText();
+ } else if (mSimErrorState[slotId]) {
+ mSimErrorState[slotId] = false;
+ updateCarrierText();
+ }
+ }
+ };
+
+ private final ActiveDataSubscriptionIdListener mPhoneStateListener =
+ new ActiveDataSubscriptionIdListener() {
+ @Override
+ public void onActiveDataSubscriptionIdChanged(int subId) {
+ if (mNetworkSupported.get() && mCarrierTextCallback != null) {
+ updateCarrierText();
+ }
+ }
+ };
+
+ /**
+ * The status of this lock screen. Primarily used for widgets on LockScreen.
+ */
+ private enum StatusMode {
+ Normal, // Normal case (sim card present, it's not locked)
+ NetworkLocked, // SIM card is 'network locked'.
+ SimMissing, // SIM card is missing.
+ SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
+ SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
+ SimLocked, // SIM card is currently locked
+ SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
+ SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+ SimIoError, // SIM card is faulty
+ SimUnknown // SIM card is unknown
+ }
+
+ /**
+ * Controller that provides updates on text with carriers names or SIM status.
+ * Used by {@link CarrierText}.
+ *
+ * @param separator Separator between different parts of the text
+ */
+ private CarrierTextManager(Context context, CharSequence separator, boolean showAirplaneMode,
+ boolean showMissingSim, @Nullable WifiManager wifiManager,
+ TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager,
+ WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor,
+ @Background Executor bgExecutor, KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ mContext = context;
+ mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
+
+ mShowAirplaneMode = showAirplaneMode;
+ mShowMissingSim = showMissingSim;
+
+ mWifiManager = wifiManager;
+ mTelephonyManager = telephonyManager;
+ mSeparator = separator;
+ mTelephonyListenerManager = telephonyListenerManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
+ mSimErrorState = new boolean[mSimSlotsNumber];
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mBgExecutor.execute(() -> {
+ boolean supported = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+ // This will set/remove the listeners appropriately. Note that it will never double
+ // add the listeners.
+ handleSetListening(mCarrierTextCallback);
+ }
+ });
+ }
+
+ private TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ /**
+ * Checks if there are faulty cards. Adds the text depending on the slot of the card
+ *
+ * @param text: current carrier text based on the sim state
+ * @param carrierNames names order by subscription order
+ * @param subOrderBySlot array containing the sub index for each slot ID
+ * @param noSims: whether a valid sim card is inserted
+ * @return text
+ */
+ private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
+ CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
+ final CharSequence carrier = "";
+ CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
+ // mSimErrorState has the state of each sim indexed by slotID.
+ for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
+ if (!mSimErrorState[index]) {
+ continue;
+ }
+ // In the case when no sim cards are detected but a faulty card is inserted
+ // overwrite the text and only show "Invalid card"
+ if (noSims) {
+ return concatenate(carrierTextForSimIOError,
+ getContext().getText(
+ com.android.internal.R.string.emergency_calls_only),
+ mSeparator);
+ } else if (subOrderBySlot[index] != -1) {
+ int subIndex = subOrderBySlot[index];
+ // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
+ carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
+ carrierNames[subIndex],
+ mSeparator);
+ } else {
+ // concatenate "Invalid card" when faulty card is inserted in other slot
+ text = concatenate(text, carrierTextForSimIOError, mSeparator);
+ }
+
+ }
+ return text;
+ }
+
+ /**
+ * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
+ * (assumed false to start). In that case, the following happens:
+ * <ul>
+ * <li> If there was a registered callback, and the network is supported, it will register
+ * listeners.
+ * <li> If there was not a registered callback, it will try to remove unregistered listeners
+ * which is a no-op
+ * </ul>
+ *
+ * This call will always be processed in a background thread.
+ */
+ private void handleSetListening(CarrierTextCallback callback) {
+ if (callback != null) {
+ mCarrierTextCallback = callback;
+ if (mNetworkSupported.get()) {
+ // Keyguard update monitor expects callbacks from main thread
+ mMainExecutor.execute(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
+ } else {
+ // Don't listen and clear out the text when the device isn't a phone.
+ mMainExecutor.execute(() -> callback.updateCarrierInfo(
+ new CarrierTextCallbackInfo("", null, false, null)
+ ));
+ }
+ } else {
+ mCarrierTextCallback = null;
+ mMainExecutor.execute(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
+ }
+ }
+
+ /**
+ * Sets the listening status of this controller. If the callback is null, it is set to
+ * not listening.
+ *
+ * @param callback Callback to provide text updates
+ */
+ public void setListening(CarrierTextCallback callback) {
+ mBgExecutor.execute(() -> handleSetListening(callback));
+ }
+
+ protected List<SubscriptionInfo> getSubscriptionInfo() {
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ }
+
+ protected void updateCarrierText() {
+ boolean allSimsMissing = true;
+ boolean anySimReadyAndInService = false;
+ CharSequence displayText = null;
+ List<SubscriptionInfo> subs = getSubscriptionInfo();
+
+ final int numSubs = subs.size();
+ final int[] subsIds = new int[numSubs];
+ // This array will contain in position i, the index of subscription in slot ID i.
+ // -1 if no subscription in that slot
+ final int[] subOrderBySlot = new int[mSimSlotsNumber];
+ for (int i = 0; i < mSimSlotsNumber; i++) {
+ subOrderBySlot[i] = -1;
+ }
+ final CharSequence[] carrierNames = new CharSequence[numSubs];
+ if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
+
+ for (int i = 0; i < numSubs; i++) {
+ int subId = subs.get(i).getSubscriptionId();
+ carrierNames[i] = "";
+ subsIds[i] = subId;
+ subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
+ int simState = mKeyguardUpdateMonitor.getSimState(subId);
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+ if (DEBUG) {
+ Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+ }
+ if (carrierTextForSimState != null) {
+ allSimsMissing = false;
+ carrierNames[i] = carrierTextForSimState;
+ }
+ if (simState == TelephonyManager.SIM_STATE_READY) {
+ ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+ if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
+ // hack for WFC (IWLAN) not turning off immediately once
+ // Wi-Fi is disassociated or disabled
+ if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+ || (mWifiManager != null && mWifiManager.isWifiEnabled()
+ && mWifiManager.getConnectionInfo() != null
+ && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+ if (DEBUG) {
+ Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+ }
+ anySimReadyAndInService = true;
+ }
+ }
+ }
+ }
+ // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
+ // This condition will also be true always when numSubs == 0
+ if (allSimsMissing && !anySimReadyAndInService) {
+ if (numSubs != 0) {
+ // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+ // This depends on mPlmn containing the text "Emergency calls only" when the radio
+ // has some connectivity. Otherwise, it should be null or empty and just show
+ // "No SIM card"
+ // Grab the first subscripton, because they all should contain the emergency text,
+ // described above.
+ displayText = makeCarrierStringOnEmergencyCapable(
+ getMissingSimMessage(), subs.get(0).getCarrierName());
+ } else {
+ // We don't have a SubscriptionInfo to get the emergency calls only from.
+ // Grab it from the old sticky broadcast if possible instead. We can use it
+ // here because no subscriptions are active, so we don't have
+ // to worry about MSIM clashing.
+ CharSequence text =
+ getContext().getText(com.android.internal.R.string.emergency_calls_only);
+ Intent i = getContext().registerReceiver(null,
+ new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
+ if (i != null) {
+ String spn = "";
+ String plmn = "";
+ if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
+ spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
+ }
+ if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
+ plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
+ }
+ if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+ if (Objects.equals(plmn, spn)) {
+ text = plmn;
+ } else {
+ text = concatenate(plmn, spn, mSeparator);
+ }
+ }
+ displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
+ }
+ }
+
+ if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
+
+ displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
+ allSimsMissing);
+
+ boolean airplaneMode = false;
+ // APM (airplane mode) != no carrier state. There are carrier services
+ // (e.g. WFC = Wi-Fi calling) which may operate in APM.
+ if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
+ displayText = getAirplaneModeMessage();
+ airplaneMode = true;
+ }
+
+ final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
+ displayText,
+ carrierNames,
+ !allSimsMissing,
+ subsIds,
+ airplaneMode);
+ postToCallback(info);
+ }
+
+ @VisibleForTesting
+ protected void postToCallback(CarrierTextCallbackInfo info) {
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) {
+ mMainExecutor.execute(() -> callback.updateCarrierInfo(info));
+ }
+ }
+
+ private Context getContext() {
+ return mContext;
+ }
+
+ private String getMissingSimMessage() {
+ return mShowMissingSim && mTelephonyCapable
+ ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
+ }
+
+ private String getAirplaneModeMessage() {
+ return mShowAirplaneMode
+ ? getContext().getString(R.string.airplane_mode) : "";
+ }
+
+ /**
+ * Top-level function for creating carrier text. Makes text based on simState, PLMN
+ * and SPN as well as device capabilities, such as being emergency call capable.
+ *
+ * @return Carrier text if not in missing state, null otherwise.
+ */
+ private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
+ CharSequence carrierText = null;
+ CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case Normal:
+ carrierText = text;
+ break;
+
+ case SimNotReady:
+ // Null is reserved for denoting missing, in this case we have nothing to display.
+ carrierText = ""; // nothing to display yet.
+ break;
+
+ case NetworkLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ mContext.getText(R.string.keyguard_network_locked_message), text);
+ break;
+
+ case SimMissing:
+ carrierText = null;
+ break;
+
+ case SimPermDisabled:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(
+ R.string.keyguard_permanent_disabled_sim_message_short),
+ text);
+ break;
+
+ case SimMissingLocked:
+ carrierText = null;
+ break;
+
+ case SimLocked:
+ carrierText = makeCarrierStringOnLocked(
+ getContext().getText(R.string.keyguard_sim_locked_message),
+ text);
+ break;
+
+ case SimPukLocked:
+ carrierText = makeCarrierStringOnLocked(
+ getContext().getText(R.string.keyguard_sim_puk_locked_message),
+ text);
+ break;
+ case SimIoError:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_error_message_short),
+ text);
+ break;
+ case SimUnknown:
+ carrierText = null;
+ break;
+ }
+
+ return carrierText;
+ }
+
+ /*
+ * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+ */
+ private CharSequence makeCarrierStringOnEmergencyCapable(
+ CharSequence simMessage, CharSequence emergencyCallMessage) {
+ if (mIsEmergencyCallCapable) {
+ return concatenate(simMessage, emergencyCallMessage, mSeparator);
+ }
+ return simMessage;
+ }
+
+ /*
+ * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
+ * DSDS
+ */
+ private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
+ CharSequence carrierName) {
+ final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
+ final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
+ if (simMessageValid && carrierNameValid) {
+ return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
+ carrierName, simMessage);
+ } else if (simMessageValid) {
+ return simMessage;
+ } else if (carrierNameValid) {
+ return carrierName;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Determine the current status of the lock screen given the SIM state and other stuff.
+ */
+ private CarrierTextManager.StatusMode getStatusForIccState(int simState) {
+ final boolean missingAndNotProvisioned =
+ !mKeyguardUpdateMonitor.isDeviceProvisioned()
+ && (simState == TelephonyManager.SIM_STATE_ABSENT
+ || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
+
+ // Assume we're NETWORK_LOCKED if not provisioned
+ simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
+ switch (simState) {
+ case TelephonyManager.SIM_STATE_ABSENT:
+ return CarrierTextManager.StatusMode.SimMissing;
+ case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
+ return CarrierTextManager.StatusMode.SimMissingLocked;
+ case TelephonyManager.SIM_STATE_NOT_READY:
+ return CarrierTextManager.StatusMode.SimNotReady;
+ case TelephonyManager.SIM_STATE_PIN_REQUIRED:
+ return CarrierTextManager.StatusMode.SimLocked;
+ case TelephonyManager.SIM_STATE_PUK_REQUIRED:
+ return CarrierTextManager.StatusMode.SimPukLocked;
+ case TelephonyManager.SIM_STATE_READY:
+ return CarrierTextManager.StatusMode.Normal;
+ case TelephonyManager.SIM_STATE_PERM_DISABLED:
+ return CarrierTextManager.StatusMode.SimPermDisabled;
+ case TelephonyManager.SIM_STATE_UNKNOWN:
+ return CarrierTextManager.StatusMode.SimUnknown;
+ case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
+ return CarrierTextManager.StatusMode.SimIoError;
+ }
+ return CarrierTextManager.StatusMode.SimUnknown;
+ }
+
+ private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
+ CharSequence separator) {
+ final boolean plmnValid = !TextUtils.isEmpty(plmn);
+ final boolean spnValid = !TextUtils.isEmpty(spn);
+ if (plmnValid && spnValid) {
+ return new StringBuilder().append(plmn).append(separator).append(spn).toString();
+ } else if (plmnValid) {
+ return plmn;
+ } else if (spnValid) {
+ return spn;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
+ * separator added so there are no extra separators that are not needed.
+ */
+ private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
+ int length = sequences.length;
+ if (length == 0) return "";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (!TextUtils.isEmpty(sequences[i])) {
+ if (!TextUtils.isEmpty(sb)) {
+ sb.append(separator);
+ }
+ sb.append(sequences[i]);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
+ if (!TextUtils.isEmpty(string)) {
+ list.add(string);
+ }
+ return list;
+ }
+
+ private CharSequence getCarrierHelpTextForSimState(int simState,
+ String plmn, String spn) {
+ int carrierHelpTextId = 0;
+ CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case NetworkLocked:
+ carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
+ break;
+
+ case SimMissing:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
+ break;
+
+ case SimPermDisabled:
+ carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
+ break;
+
+ case SimMissingLocked:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
+ break;
+
+ case Normal:
+ case SimLocked:
+ case SimPukLocked:
+ break;
+ }
+
+ return mContext.getText(carrierHelpTextId);
+ }
+
+ /** Injectable Buildeer for {@#link CarrierTextManager}. */
+ public static class Builder {
+ private final Context mContext;
+ private final String mSeparator;
+ private final WifiManager mWifiManager;
+ private final TelephonyManager mTelephonyManager;
+ private final TelephonyListenerManager mTelephonyListenerManager;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final Executor mMainExecutor;
+ private final Executor mBgExecutor;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private boolean mShowAirplaneMode;
+ private boolean mShowMissingSim;
+
+ @Inject
+ public Builder(Context context, @Main Resources resources,
+ @Nullable WifiManager wifiManager,
+ TelephonyManager telephonyManager,
+ TelephonyListenerManager telephonyListenerManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ @Main Executor mainExecutor, @Background Executor bgExecutor,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ mContext = context;
+ mSeparator = resources.getString(
+ com.android.internal.R.string.kg_text_message_separator);
+ mWifiManager = wifiManager;
+ mTelephonyManager = telephonyManager;
+ mTelephonyListenerManager = telephonyListenerManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ /** */
+ public Builder setShowAirplaneMode(boolean showAirplaneMode) {
+ mShowAirplaneMode = showAirplaneMode;
+ return this;
+ }
+
+ /** */
+ public Builder setShowMissingSim(boolean showMissingSim) {
+ mShowMissingSim = showMissingSim;
+ return this;
+ }
+
+ /** Create a CarrierTextManager. */
+ public CarrierTextManager build() {
+ return new CarrierTextManager(
+ mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+ mTelephonyManager, mTelephonyListenerManager,
+ mWakefulnessLifecycle, mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
+ }
+ }
+ /**
+ * Data structure for passing information to CarrierTextController subscribers
+ */
+ public static final class CarrierTextCallbackInfo {
+ public final CharSequence carrierText;
+ public final CharSequence[] listOfCarriers;
+ public final boolean anySimReady;
+ public final int[] subscriptionIds;
+ public boolean airplaneMode;
+
+ @VisibleForTesting
+ public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+ boolean anySimReady, int[] subscriptionIds) {
+ this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
+ }
+
+ @VisibleForTesting
+ public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+ boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
+ this.carrierText = carrierText;
+ this.listOfCarriers = listOfCarriers;
+ this.anySimReady = anySimReady;
+ this.subscriptionIds = subscriptionIds;
+ this.airplaneMode = airplaneMode;
+ }
+ }
+
+ /**
+ * Callback to communicate to Views
+ */
+ public interface CarrierTextCallback {
+ /**
+ * Provides updated carrier information.
+ */
+ default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
+
+ /**
+ * Notifies the View that the device is going to sleep
+ */
+ default void startedGoingToSleep() {};
+
+ /**
+ * Notifies the View that the device finished waking up
+ */
+ default void finishedWakingUp() {};
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 707ee29..c4b02f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -16,34 +16,16 @@
package com.android.keyguard;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Button;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
-import com.android.systemui.util.EmergencyDialerConstants;
/**
* This class implements a smart emergency button that updates itself based
@@ -53,34 +35,14 @@
*/
public class EmergencyButton extends Button {
- private static final String LOG_TAG = "EmergencyButton";
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
private int mDownX;
private int mDownY;
- KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
-
- @Override
- public void onSimStateChanged(int subId, int slotId, int simState) {
- updateEmergencyCallButton();
- }
-
- @Override
- public void onPhoneStateChanged(int phoneState) {
- updateEmergencyCallButton();
- }
- };
private boolean mLongPressWasDragged;
- public interface EmergencyButtonCallback {
- public void onEmergencyButtonClickedWhenInCall();
- }
-
private LockPatternUtils mLockPatternUtils;
- private PowerManager mPowerManager;
- private EmergencyButtonCallback mEmergencyButtonCallback;
- private final boolean mIsVoiceCapable;
private final boolean mEnableEmergencyCallWhileSimLocked;
public EmergencyButton(Context context) {
@@ -89,34 +51,15 @@
public EmergencyButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mIsVoiceCapable = getTelephonyManager().isVoiceCapable();
mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
}
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- setOnClickListener(v -> takeEmergencyCallAction());
if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
setOnLongClickListener(v -> {
if (!mLongPressWasDragged
@@ -127,7 +70,6 @@
return false;
});
}
- whitelistIpcs(this::updateEmergencyCallButton);
}
@Override
@@ -165,65 +107,13 @@
return super.performLongClick();
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateEmergencyCallButton();
- }
-
- /**
- * Shows the emergency dialer or returns the user to the existing call.
- */
- public void takeEmergencyCallAction() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
- if (mPowerManager != null) {
- mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
- }
- try {
- ActivityTaskManager.getService().stopSystemLockTaskMode();
- } catch (RemoteException e) {
- Slog.w(LOG_TAG, "Failed to stop app pinning");
- }
- if (isInCall()) {
- resumeCall();
- if (mEmergencyButtonCallback != null) {
- mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
- }
- } else {
- KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- if (updateMonitor != null) {
- updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
- } else {
- Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
- }
- TelecomManager telecomManager = getTelecommManager();
- if (telecomManager == null) {
- Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
- return;
- }
- Intent emergencyDialIntent =
- telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_CLEAR_TOP)
- .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
- EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
-
- getContext().startActivityAsUser(emergencyDialIntent,
- ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
- new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
- }
- }
-
- private void updateEmergencyCallButton() {
+ void updateEmergencyCallButton(boolean isInCall, boolean isVoiceCapable, boolean simLocked) {
boolean visible = false;
- if (mIsVoiceCapable) {
+ if (isVoiceCapable) {
// Emergency calling requires voice capability.
- if (isInCall()) {
+ if (isInCall) {
visible = true; // always show "return to call" if phone is off-hook
} else {
- final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class)
- .isSimPinVoiceSecure();
if (simLocked) {
// Some countries can't handle emergency calls while SIM is locked.
visible = mEnableEmergencyCallWhileSimLocked;
@@ -237,7 +127,7 @@
setVisibility(View.VISIBLE);
int textId;
- if (isInCall()) {
+ if (isInCall) {
textId = com.android.internal.R.string.lockscreen_return_to_call;
} else {
textId = com.android.internal.R.string.lockscreen_emergency_call;
@@ -247,26 +137,4 @@
setVisibility(View.GONE);
}
}
-
- public void setCallback(EmergencyButtonCallback callback) {
- mEmergencyButtonCallback = callback;
- }
-
- /**
- * Resumes a call in progress.
- */
- private void resumeCall() {
- getTelecommManager().showInCallScreen(false);
- }
-
- /**
- * @return {@code true} if there is a call currently in progress.
- */
- private boolean isInCall() {
- return getTelecommManager().isInCall();
- }
-
- private TelecomManager getTelecommManager() {
- return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
new file mode 100644
index 0000000..4275189
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.android.systemui.DejankUtils.whitelistIpcs;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.util.EmergencyDialerConstants;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.keyguard.EmergencyButton}. */
+@KeyguardBouncerScope
+public class EmergencyButtonController extends ViewController<EmergencyButton> {
+ static final String LOG_TAG = "EmergencyButton";
+ private final ConfigurationController mConfigurationController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final TelephonyManager mTelephonyManager;
+ private final PowerManager mPowerManager;
+ private final ActivityTaskManager mActivityTaskManager;
+ private final TelecomManager mTelecomManager;
+ private final MetricsLogger mMetricsLogger;
+
+ private EmergencyButtonCallback mEmergencyButtonCallback;
+
+ private final KeyguardUpdateMonitorCallback mInfoCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ updateEmergencyCallButton();
+ }
+
+ @Override
+ public void onPhoneStateChanged(int phoneState) {
+ updateEmergencyCallButton();
+ }
+ };
+
+ private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateEmergencyCallButton();
+ }
+ };
+
+ private EmergencyButtonController(@Nullable EmergencyButton view,
+ ConfigurationController configurationController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+ PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+ super(view);
+ mConfigurationController = configurationController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mTelephonyManager = telephonyManager;
+ mPowerManager = powerManager;
+ mActivityTaskManager = activityTaskManager;
+ mTelecomManager = telecomManager;
+ mMetricsLogger = metricsLogger;
+ }
+
+ @Override
+ protected void onInit() {
+ whitelistIpcs(this::updateEmergencyCallButton);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+ mConfigurationController.addCallback(mConfigurationListener);
+ mView.setOnClickListener(v -> takeEmergencyCallAction());
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ }
+
+ private void updateEmergencyCallButton() {
+ if (mView != null) {
+ mView.updateEmergencyCallButton(
+ mTelecomManager != null && mTelecomManager.isInCall(),
+ mTelephonyManager.isVoiceCapable(),
+ mKeyguardUpdateMonitor.isSimPinVoiceSecure());
+ }
+ }
+
+ public void setEmergencyButtonCallback(EmergencyButtonCallback callback) {
+ mEmergencyButtonCallback = callback;
+ }
+ /**
+ * Shows the emergency dialer or returns the user to the existing call.
+ */
+ public void takeEmergencyCallAction() {
+ mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL);
+ if (mPowerManager != null) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ }
+ mActivityTaskManager.stopSystemLockTaskMode();
+ if (mTelecomManager != null && mTelecomManager.isInCall()) {
+ mTelecomManager.showInCallScreen(false);
+ if (mEmergencyButtonCallback != null) {
+ mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
+ }
+ } else {
+ mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+ if (mTelecomManager == null) {
+ Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+ return;
+ }
+ Intent emergencyDialIntent =
+ mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+ EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
+
+ getContext().startActivityAsUser(emergencyDialIntent,
+ ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
+ new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+ }
+ }
+
+ /** */
+ public interface EmergencyButtonCallback {
+ /** */
+ void onEmergencyButtonClickedWhenInCall();
+ }
+
+ /** Injectable Factory for creating {@link EmergencyButtonController}. */
+ public static class Factory {
+ private final ConfigurationController mConfigurationController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final TelephonyManager mTelephonyManager;
+ private final PowerManager mPowerManager;
+ private final ActivityTaskManager mActivityTaskManager;
+ @Nullable
+ private final TelecomManager mTelecomManager;
+ private final MetricsLogger mMetricsLogger;
+
+ @Inject
+ public Factory(ConfigurationController configurationController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+ PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+
+ mConfigurationController = configurationController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mTelephonyManager = telephonyManager;
+ mPowerManager = powerManager;
+ mActivityTaskManager = activityTaskManager;
+ mTelecomManager = telecomManager;
+ mMetricsLogger = metricsLogger;
+ }
+
+ /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
+ public EmergencyButtonController create(EmergencyButton view) {
+ return new EmergencyButtonController(view, mConfigurationController,
+ mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
+ mTelecomManager, mMetricsLogger);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 9f32c03..e41d5a3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -31,7 +31,7 @@
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
@@ -44,6 +44,7 @@
private final LockPatternUtils mLockPatternUtils;
private final LatencyTracker mLatencyTracker;
private final FalsingCollector mFalsingCollector;
+ private final EmergencyButtonController mEmergencyButtonController;
private CountDownTimer mCountdownTimer;
protected KeyguardMessageAreaController mMessageAreaController;
private boolean mDismissing;
@@ -73,12 +74,14 @@
LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker, FalsingCollector falsingCollector) {
- super(view, securityMode, keyguardSecurityCallback);
+ LatencyTracker latencyTracker, FalsingCollector falsingCollector,
+ EmergencyButtonController emergencyButtonController) {
+ super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
mFalsingCollector = falsingCollector;
+ mEmergencyButtonController = emergencyButtonController;
KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
mMessageAreaController = messageAreaControllerFactory.create(kma);
}
@@ -87,6 +90,7 @@
@Override
public void onInit() {
+ super.onInit();
mMessageAreaController.init();
}
@@ -95,10 +99,7 @@
super.onViewAttached();
mView.setKeyDownListener(mKeyDownListener);
mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
- EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(mEmergencyButtonCallback);
- }
+ mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 276036c..76a7473 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -36,7 +36,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -46,12 +45,15 @@
import javax.inject.Inject;
+import dagger.Lazy;
+
public class KeyguardDisplayManager {
protected static final String TAG = "KeyguardDisplayManager";
private static boolean DEBUG = KeyguardConstants.DEBUG;
private MediaRouter mMediaRouter = null;
private final DisplayManager mDisplayService;
+ private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
private final Context mContext;
@@ -85,9 +87,11 @@
@Inject
public KeyguardDisplayManager(Context context,
+ Lazy<NavigationBarController> navigationBarControllerLazy,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@UiBackground Executor uiBgExecutor) {
mContext = context;
+ mNavigationBarControllerLazy = navigationBarControllerLazy;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
mDisplayService = mContext.getSystemService(DisplayManager.class);
@@ -240,7 +244,7 @@
// Leave this task to {@link StatusBarKeyguardViewManager}
if (displayId == DEFAULT_DISPLAY) return;
- NavigationBarView navBarView = Dependency.get(NavigationBarController.class)
+ NavigationBarView navBarView = mNavigationBarControllerLazy.get()
.getNavigationBarView(displayId);
// We may not have nav bar on a display.
if (navBarView == null) return;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 05f33a9..3d42da2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -42,6 +42,7 @@
private final SecurityMode mSecurityMode;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final EmergencyButton mEmergencyButton;
+ private final EmergencyButtonController mEmergencyButtonController;
private boolean mPaused;
@@ -69,11 +70,18 @@
};
protected KeyguardInputViewController(T view, SecurityMode securityMode,
- KeyguardSecurityCallback keyguardSecurityCallback) {
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ EmergencyButtonController emergencyButtonController) {
super(view);
mSecurityMode = securityMode;
mKeyguardSecurityCallback = keyguardSecurityCallback;
mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
+ mEmergencyButtonController = emergencyButtonController;
+ }
+
+ @Override
+ protected void onInit() {
+ mEmergencyButtonController.init();
}
@Override
@@ -157,6 +165,7 @@
private final Resources mResources;
private final LiftToActivateListener mLiftToActivateListener;
private final TelephonyManager mTelephonyManager;
+ private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final FalsingCollector mFalsingCollector;
private final boolean mIsNewLayoutEnabled;
@@ -168,6 +177,7 @@
InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
@Main Resources resources, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
+ EmergencyButtonController.Factory emergencyButtonControllerFactory,
FeatureFlags featureFlags) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
@@ -178,6 +188,7 @@
mResources = resources;
mLiftToActivateListener = liftToActivateListener;
mTelephonyManager = telephonyManager;
+ mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled();
}
@@ -185,33 +196,40 @@
/** Create a new {@link KeyguardInputViewController}. */
public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
+ EmergencyButtonController emergencyButtonController =
+ mEmergencyButtonControllerFactory.create(
+ keyguardInputView.findViewById(R.id.emergency_call_button));
+
if (keyguardInputView instanceof KeyguardPatternView) {
return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
- mMessageAreaControllerFactory);
+ emergencyButtonController, mMessageAreaControllerFactory);
} else if (keyguardInputView instanceof KeyguardPasswordView) {
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mInputMethodManager, mMainExecutor, mResources, mFalsingCollector);
+ mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
+ mFalsingCollector);
+
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mFalsingCollector, mIsNewLayoutEnabled);
+ mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+ mIsNewLayoutEnabled);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- mIsNewLayoutEnabled);
+ emergencyButtonController, mIsNewLayoutEnabled);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- mIsNewLayoutEnabled);
+ emergencyButtonController, mIsNewLayoutEnabled);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index e45dd8b..933a919 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -112,11 +112,13 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
InputMethodManager inputMethodManager,
+ EmergencyButtonController emergencyButtonController,
@Main DelayableExecutor mainExecutor,
@Main Resources resources,
FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, falsingCollector);
+ messageAreaControllerFactory, latencyTracker, falsingCollector,
+ emergencyButtonController);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index e16c01a..f0d1e02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -32,7 +32,7 @@
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockPatternView.Cell;
import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -54,6 +54,7 @@
private final LockPatternUtils mLockPatternUtils;
private final LatencyTracker mLatencyTracker;
private final FalsingCollector mFalsingCollector;
+ private final EmergencyButtonController mEmergencyButtonController;
private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
private KeyguardMessageAreaController mMessageAreaController;
@@ -189,12 +190,14 @@
KeyguardSecurityCallback keyguardSecurityCallback,
LatencyTracker latencyTracker,
FalsingCollector falsingCollector,
+ EmergencyButtonController emergencyButtonController,
KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
- super(view, securityMode, keyguardSecurityCallback);
+ super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
mFalsingCollector = falsingCollector;
+ mEmergencyButtonController = emergencyButtonController;
mMessageAreaControllerFactory = messageAreaControllerFactory;
KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
mMessageAreaController = mMessageAreaControllerFactory.create(kma);
@@ -222,11 +225,7 @@
}
return false;
});
-
- EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(mEmergencyButtonCallback);
- }
+ mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
View cancelBtn = mView.findViewById(R.id.cancel_button);
if (cancelBtn != null) {
@@ -242,10 +241,7 @@
super.onViewDetached();
mLockPatternView.setOnPatternListener(null);
mLockPatternView.setOnTouchListener(null);
- EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
- if (button != null) {
- button.setCallback(null);
- }
+ mEmergencyButtonController.setEmergencyButtonCallback(null);
View cancelBtn = mView.findViewById(R.id.cancel_button);
if (cancelBtn != null) {
cancelBtn.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index b156f81..8de4e7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -57,9 +57,11 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener,
+ EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, falsingCollector);
+ messageAreaControllerFactory, latencyTracker, falsingCollector,
+ emergencyButtonController);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 49099fa..a456d42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -34,10 +34,11 @@
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- falsingCollector);
+ emergencyButtonController, falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
view.setIsNewLayoutEnabled(isNewLayoutEnabled);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 631c248..69328cd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -23,7 +23,6 @@
import android.telephony.TelephonyManager;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -49,24 +48,27 @@
private final boolean mIsPukScreenAvailable;
private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Inject
- KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
+ KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mIsPukScreenAvailable = resources.getBoolean(
com.android.internal.R.bool.config_enable_puk_unlock_screen);
mLockPatternUtils = lockPatternUtils;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}
public SecurityMode getSecurityMode(int userId) {
- KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-
if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
- monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
+ mKeyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
return SecurityMode.SimPuk;
}
if (SubscriptionManager.isValidSubscriptionId(
- monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
+ mKeyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
return SecurityMode.SimPin;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index f1b504e..33d47fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -44,15 +44,18 @@
private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
new ArrayList<>();
private final LayoutInflater mLayoutInflater;
+ private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final Factory mKeyguardSecurityViewControllerFactory;
@Inject
protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
LayoutInflater layoutInflater,
- KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
+ KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
+ EmergencyButtonController.Factory emergencyButtonControllerFactory) {
super(view);
mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
mLayoutInflater = layoutInflater;
+ mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
}
@Override
@@ -111,7 +114,8 @@
if (childController == null) {
childController = new NullKeyguardInputViewController(
- securityMode, keyguardSecurityCallback);
+ securityMode, keyguardSecurityCallback,
+ mEmergencyButtonControllerFactory.create(null));
}
return childController;
@@ -140,8 +144,9 @@
private static class NullKeyguardInputViewController
extends KeyguardInputViewController<KeyguardInputView> {
protected NullKeyguardInputViewController(SecurityMode securityMode,
- KeyguardSecurityCallback keyguardSecurityCallback) {
- super(null, securityMode, keyguardSecurityCallback);
+ KeyguardSecurityCallback keyguardSecurityCallback,
+ EmergencyButtonController emergencyButtonController) {
+ super(null, securityMode, keyguardSecurityCallback, emergencyButtonController);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index b2bf2e6..fddbb3c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -79,10 +79,10 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
- boolean isNewLayoutEnabled) {
+ EmergencyButtonController emergencyButtonController, boolean isNewLayoutEnabled) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- falsingCollector);
+ emergencyButtonController, falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 620db48..50bd0c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,7 +37,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
@@ -86,10 +85,10 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
- boolean isNewLayoutEnabled) {
+ EmergencyButtonController emergencyButtonController, boolean isNewLayoutEnabled) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- falsingCollector);
+ emergencyButtonController, falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -198,8 +197,7 @@
if (count < 2) {
msg = rez.getString(R.string.kg_puk_enter_puk_hint);
} else {
- SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
- .getSubscriptionInfoForSubId(mSubId);
+ SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
CharSequence displayName = info != null ? info.getDisplayName() : "";
msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
if (info != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 83c2d1e..96e69ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -49,10 +49,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
import java.io.FileDescriptor;
@@ -299,8 +297,23 @@
void onDensityOrFontScaleChanged() {
mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size);
mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size);
+
+ for (int i = 0; i < mRow.getChildCount(); i++) {
+ View child = mRow.getChildAt(i);
+ if (child instanceof KeyguardSliceTextView) {
+ ((KeyguardSliceTextView) child).onDensityOrFontScaleChanged();
+ }
+ }
}
+ void onOverlayChanged() {
+ for (int i = 0; i < mRow.getChildCount(); i++) {
+ View child = mRow.getChildAt(i);
+ if (child instanceof KeyguardSliceTextView) {
+ ((KeyguardSliceTextView) child).onOverlayChanged();
+ }
+ }
+ }
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardSliceView:");
pw.println(" mTitle: " + (mTitle == null ? "null" : mTitle.getVisibility() == VISIBLE));
@@ -466,8 +479,7 @@
* Representation of an item that appears under the clock on main keyguard message.
*/
@VisibleForTesting
- static class KeyguardSliceTextView extends TextView implements
- ConfigurationController.ConfigurationListener {
+ static class KeyguardSliceTextView extends TextView {
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@StyleRes
@@ -479,24 +491,10 @@
setEllipsize(TruncateAt.END);
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(ConfigurationController.class).addCallback(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(ConfigurationController.class).removeCallback(this);
- }
-
- @Override
public void onDensityOrFontScaleChanged() {
updatePadding();
}
- @Override
public void onOverlayChanged() {
setTextAppearance(sStyleId);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 1b0a7fa..8038ce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -83,6 +83,10 @@
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
}
+ @Override
+ public void onOverlayChanged() {
+ mView.onOverlayChanged();
+ }
};
Observer<Slice> mObserver = new Observer<Slice>() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index fea152a..5db4f9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -34,7 +34,6 @@
import androidx.core.graphics.ColorUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import java.io.FileDescriptor;
@@ -56,7 +55,6 @@
private final IActivityManager mIActivityManager;
private TextView mLogoutView;
- private boolean mCanShowLogout = true; // by default, try to show the logout button here
private KeyguardClockSwitch mClockView;
private TextView mOwnerInfo;
private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here
@@ -130,11 +128,6 @@
}
}
- void setCanShowLogout(boolean canShowLogout) {
- mCanShowLogout = canShowLogout;
- updateLogoutView();
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -159,10 +152,7 @@
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
onSliceContentChanged();
- boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
- setEnableMarquee(shouldMarquee);
updateOwnerInfo();
- updateLogoutView();
updateDark();
}
@@ -209,11 +199,11 @@
return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0;
}
- void updateLogoutView() {
+ void updateLogoutView(boolean shouldShowLogout) {
if (mLogoutView == null) {
return;
}
- mLogoutView.setVisibility(mCanShowLogout && shouldShowLogout() ? VISIBLE : GONE);
+ mLogoutView.setVisibility(shouldShowLogout ? VISIBLE : GONE);
// Logout button will stay in language of user 0 if we don't set that manually.
mLogoutView.setText(mContext.getResources().getString(
com.android.internal.R.string.global_action_logout));
@@ -313,11 +303,6 @@
}
}
- private boolean shouldShowLogout() {
- return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
- && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
- }
-
private void onLogoutClicked(View view) {
int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 934e768..31ec003 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import android.os.UserHandle;
import android.util.Slog;
import android.view.View;
@@ -78,6 +79,8 @@
@Override
public void onInit() {
mKeyguardClockSwitchController.init();
+ mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
+ mView.updateLogoutView(shouldShowLogout());
}
@Override
@@ -245,6 +248,11 @@
}
}
+ private boolean shouldShowLogout() {
+ return mKeyguardUpdateMonitor.isLogoutEnabled()
+ && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+ }
+
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
@@ -267,10 +275,10 @@
mKeyguardSliceViewController.updateLockScreenMode(mode);
if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
mView.setCanShowOwnerInfo(false);
- mView.setCanShowLogout(false);
+ mView.updateLogoutView(false);
} else {
mView.setCanShowOwnerInfo(true);
- mView.setCanShowLogout(false);
+ mView.updateLogoutView(false);
}
updateAodIcons();
}
@@ -296,7 +304,7 @@
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
refreshTime();
mView.updateOwnerInfo();
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
}
@@ -314,12 +322,12 @@
public void onUserSwitchComplete(int userId) {
mKeyguardClockSwitchController.refreshFormat();
mView.updateOwnerInfo();
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
@Override
public void onLogoutEnabledChanged() {
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
};
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 69e6ed0..9abc1e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -22,7 +22,6 @@
import static android.content.Intent.ACTION_USER_STOPPED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -76,11 +75,11 @@
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.SparseArray;
@@ -107,6 +106,7 @@
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
import com.android.systemui.util.RingerModeTracker;
@@ -290,6 +290,7 @@
private boolean mDeviceInteractive;
private boolean mScreenOn;
private SubscriptionManager mSubscriptionManager;
+ private final TelephonyListenerManager mTelephonyListenerManager;
private List<SubscriptionInfo> mSubscriptionInfo;
private TrustManager mTrustManager;
private UserManager mUserManager;
@@ -358,7 +359,8 @@
};
@VisibleForTesting
- public PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ public TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener =
+ new TelephonyCallback.ActiveDataSubscriptionIdListener() {
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
mActiveMobileDataSubscription = subId;
@@ -1614,9 +1616,11 @@
StatusBarStateController statusBarStateController,
LockPatternUtils lockPatternUtils,
AuthController authController,
+ TelephonyListenerManager telephonyListenerManager,
FeatureFlags featureFlags) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
+ mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
mBackgroundExecutor = backgroundExecutor;
@@ -1865,8 +1869,7 @@
mTelephonyManager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephonyManager != null) {
- mTelephonyManager.listen(mPhoneStateListener,
- LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
// Set initial sim states values.
for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
int state = mTelephonyManager.getSimState(slot);
@@ -3123,7 +3126,7 @@
TelephonyManager telephony =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (telephony != null) {
- telephony.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
}
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
new file mode 100644
index 0000000..c4be1ba535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.clock;
+
+import java.util.List;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clock package. */
+@Module
+public abstract class ClockModule {
+
+ /** */
+ @Provides
+ public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) {
+ return clockManager.getClockInfos();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
index 5ef35be..b6413cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
@@ -28,11 +28,12 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
import java.io.FileNotFoundException;
import java.util.List;
-import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Exposes custom clock face options and provides realistic preview images.
@@ -65,15 +66,12 @@
private static final String CONTENT_SCHEME = "content";
private static final String AUTHORITY = "com.android.keyguard.clock";
- private final Supplier<List<ClockInfo>> mClocksSupplier;
-
- public ClockOptionsProvider() {
- this(() -> Dependency.get(ClockManager.class).getClockInfos());
- }
+ @Inject
+ public Provider<List<ClockInfo>> mClockInfosProvider;
@VisibleForTesting
- ClockOptionsProvider(Supplier<List<ClockInfo>> clocksSupplier) {
- mClocksSupplier = clocksSupplier;
+ ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) {
+ mClockInfosProvider = clockInfosProvider;
}
@Override
@@ -99,7 +97,7 @@
}
MatrixCursor cursor = new MatrixCursor(new String[] {
COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
- List<ClockInfo> clocks = mClocksSupplier.get();
+ List<ClockInfo> clocks = mClockInfosProvider.get();
for (int i = 0; i < clocks.size(); i++) {
ClockInfo clock = clocks.get(i);
cursor.newRow()
@@ -139,7 +137,7 @@
throw new FileNotFoundException("Invalid preview url, missing id");
}
ClockInfo clock = null;
- List<ClockInfo> clocks = mClocksSupplier.get();
+ List<ClockInfo> clocks = mClockInfosProvider.get();
for (int i = 0; i < clocks.size(); i++) {
if (id.equals(clocks.get(i).getId())) {
clock = clocks.get(i);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
new file mode 100644
index 0000000..49a617e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.keyguard.KeyguardStatusViewController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusViewComponent}
+ */
+@Subcomponent(modules = {KeyguardStatusBarViewModule.class})
+@KeyguardStatusBarViewScope
+public interface KeyguardStatusBarViewComponent {
+ /** Simple factory for {@link KeyguardStatusBarViewComponent}. */
+ @Subcomponent.Factory
+ interface Factory {
+ KeyguardStatusBarViewComponent build(@BindsInstance KeyguardStatusBarView view);
+ }
+
+ /** Builds a {@link KeyguardStatusViewController}. */
+ KeyguardStatusBarViewController getKeyguardStatusBarViewController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
new file mode 100644
index 0000000..a672523
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.keyguard.CarrierText;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link KeyguardStatusBarViewComponent}. */
+@Module
+public abstract class KeyguardStatusBarViewModule {
+ @Provides
+ @KeyguardStatusBarViewScope
+ static CarrierText getCarrierText(KeyguardStatusBarView view) {
+ return view.findViewById(R.id.keyguard_carrier_text);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
new file mode 100644
index 0000000..ba0642f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardStatusBarViewScope {}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
index 1b6476c..d342377 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
@@ -25,6 +25,8 @@
/**
* Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusBarViewComponent}
*/
@Subcomponent(modules = {KeyguardStatusViewModule.class})
@KeyguardStatusViewScope
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index cd53a34..ac99f73 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -349,6 +349,9 @@
}
private void setPercentTextAtCurrentLevel() {
+ if (mBatteryPercentView == null) {
+ return;
+ }
mBatteryPercentView.setText(
NumberFormat.getPercentInstance().format(mLevel / 100f));
setContentDescription(
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 06b486e..a686fc0 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -120,6 +120,7 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
@@ -350,6 +351,7 @@
@Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
@Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
@Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy;
+ @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
@Inject
public Dependency() {
@@ -545,6 +547,7 @@
mProviders.put(StatusBar.class, mStatusBar::get);
mProviders.put(ProtoTracer.class, mProtoTracer::get);
mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
+ mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 4afa969..45a0ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -32,7 +32,9 @@
import android.util.Log;
import android.view.WindowManagerGlobal;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.statusbar.phone.SystemUIDialog;
/**
@@ -45,6 +47,11 @@
private static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
private Dialog mNewSessionDialog;
+ private final UiEventLogger mUiEventLogger;
+
+ public GuestResumeSessionReceiver(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
+ }
/**
* Register this receiver with the {@link BroadcastDispatcher}
@@ -83,7 +90,7 @@
int notFirstLogin = Settings.System.getIntForUser(
cr, SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
if (notFirstLogin != 0) {
- mNewSessionDialog = new ResetSessionDialog(context, userId);
+ mNewSessionDialog = new ResetSessionDialog(context, mUiEventLogger, userId);
mNewSessionDialog.show();
} else {
Settings.System.putIntForUser(
@@ -153,9 +160,10 @@
private static final int BUTTON_WIPE = BUTTON_NEGATIVE;
private static final int BUTTON_DONTWIPE = BUTTON_POSITIVE;
+ private final UiEventLogger mUiEventLogger;
private final int mUserId;
- public ResetSessionDialog(Context context, int userId) {
+ ResetSessionDialog(Context context, UiEventLogger uiEventLogger, int userId) {
super(context);
setTitle(context.getString(R.string.guest_wipe_session_title));
@@ -167,15 +175,18 @@
setButton(BUTTON_DONTWIPE,
context.getString(R.string.guest_wipe_session_dontwipe), this);
+ mUiEventLogger = uiEventLogger;
mUserId = userId;
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == BUTTON_WIPE) {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_WIPE);
wipeGuestSession(getContext(), mUserId);
dismiss();
} else if (which == BUTTON_DONTWIPE) {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE);
cancel();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
new file mode 100644
index 0000000..a1149fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 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.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricSourceType
+import android.view.View
+import android.view.ViewGroup
+import com.android.internal.annotations.VisibleForTesting
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.settingslib.Utils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/***
+ * Controls the ripple effect that shows when authentication is successful.
+ * The ripple uses the accent color of the current theme.
+ */
+@SysUISingleton
+class AuthRippleController @Inject constructor(
+ commandRegistry: CommandRegistry,
+ configurationController: ConfigurationController,
+ private val context: Context,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor
+) {
+ @VisibleForTesting
+ var rippleView: AuthRippleView = AuthRippleView(context, attrs = null)
+
+ val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType?,
+ isStrongBiometric: Boolean
+ ) {
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ rippleView.startRipple()
+ }
+ }
+ }
+
+ init {
+ val configurationChangedListener = object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ updateRippleColor()
+ }
+ override fun onThemeChanged() {
+ updateRippleColor()
+ }
+ override fun onOverlayChanged() {
+ updateRippleColor()
+ }
+ }
+ configurationController.addCallback(configurationChangedListener)
+
+ commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
+ }
+
+ fun setSensorLocation(x: Float, y: Float) {
+ rippleView.setSensorLocation(x, y)
+ }
+
+ fun setViewHost(viewHost: View) {
+ // Add the ripple view to its host layout
+ viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+ override fun onViewDetachedFromWindow(view: View?) {}
+
+ override fun onViewAttachedToWindow(view: View?) {
+ (viewHost as ViewGroup).addView(rippleView)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ viewHost.removeOnAttachStateChangeListener(this)
+ }
+ })
+
+ updateRippleColor()
+ }
+
+ private fun updateRippleColor() {
+ rippleView.setColor(
+ Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor)
+ }
+
+ inner class AuthRippleCommand : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ rippleView.startRipple()
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar auth-ripple")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
new file mode 100644
index 0000000..2e32133
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 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.biometrics
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PointF
+import android.util.AttributeSet
+import android.view.View
+import com.android.systemui.statusbar.charging.RippleShader
+
+private const val RIPPLE_ANIMATION_DURATION: Long = 950
+private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
+
+/**
+ * Expanding ripple effect on the transition from biometric authentication success to showing
+ * launcher.
+ */
+class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+ private var rippleInProgress: Boolean = false
+ private val rippleShader = RippleShader()
+ private val defaultColor: Int = 0xffffffff.toInt()
+ private val ripplePaint = Paint()
+
+ init {
+ rippleShader.color = defaultColor
+ rippleShader.progress = 0f
+ rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+ ripplePaint.shader = rippleShader
+ visibility = View.GONE
+ }
+
+ fun setSensorLocation(x: Float, y: Float) {
+ rippleShader.origin = PointF(x, y)
+ rippleShader.radius = maxOf(x, y, width - x, height - y).toFloat()
+ }
+
+ fun startRipple() {
+ if (rippleInProgress) {
+ return // Ignore if ripple effect is already playing
+ }
+ val animator = ValueAnimator.ofFloat(0f, 1f)
+ animator.duration = RIPPLE_ANIMATION_DURATION
+ animator.addUpdateListener { animator ->
+ val now = animator.currentPlayTime
+ val phase = now / 30000f
+ rippleShader.progress = animator.animatedValue as Float
+ rippleShader.noisePhase = phase
+ invalidate()
+ }
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ rippleInProgress = false
+ visibility = View.GONE
+ }
+ })
+ animator.start()
+ visibility = View.VISIBLE
+ rippleInProgress = true
+ }
+
+ fun setColor(color: Int) {
+ rippleShader.color = color
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ // draw over the entire screen
+ canvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), ripplePaint)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 98b3fe4..078ec9f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -87,6 +87,7 @@
@NonNull private final StatusBarStateController mStatusBarStateController;
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final DumpManager mDumpManager;
+ @NonNull private final AuthRippleController mAuthRippleController;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -305,7 +306,8 @@
@Main DelayableExecutor fgExecutor,
@NonNull StatusBar statusBar,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @NonNull DumpManager dumpManager) {
+ @NonNull DumpManager dumpManager,
+ @NonNull AuthRippleController authRippleController) {
mContext = context;
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -317,6 +319,7 @@
mStatusBarStateController = statusBarStateController;
mKeyguardViewManager = statusBarKeyguardViewManager;
mDumpManager = dumpManager;
+ mAuthRippleController = authRippleController;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -343,6 +346,10 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mBroadcastReceiver, filter);
+
+ mAuthRippleController.setViewHost(mStatusBar.getNotificationShadeWindowView());
+ mAuthRippleController.setSensorLocation(getSensorLocation().centerX(),
+ getSensorLocation().centerY());
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 98a703f..521c495 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Log;
import android.util.TypedValue;
import java.util.ArrayList;
@@ -39,6 +40,9 @@
"com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
private static final float SCALE = 0.5f;
+ private static final String NEW_COORDS_OVERRIDE =
+ "com.android.systemui.biometrics.UdfpsNewCoords";
+
// Enroll with two center touches before going to guided enrollment
private static final int NUM_CENTER_TOUCHES = 2;
@@ -68,21 +72,42 @@
// Number of pixels per mm
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
context.getResources().getDisplayMetrics());
-
- mGuidedEnrollmentPoints.add(new PointF( 2.00f * px, 0.00f * px));
- mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px));
- mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
- mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px));
- mGuidedEnrollmentPoints.add(new PointF( 0.88f * px, 2.70f * px));
- mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px));
- mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px));
- mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
- mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
- mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
- mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px));
- mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px));
- mGuidedEnrollmentPoints.add(new PointF( 2.29f * px, 4.92f * px));
- mGuidedEnrollmentPoints.add(new PointF( 3.82f * px, 1.78f * px));
+ boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ NEW_COORDS_OVERRIDE, 0,
+ UserHandle.USER_CURRENT) != 0;
+ if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
+ Log.v(TAG, "Using new coordinates");
+ mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 0.29f * px, 0.00f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, -2.35f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, -3.96f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, 3.96f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, 2.35f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 2.58f * px, 0.00f * px));
+ } else {
+ Log.v(TAG, "Using old coordinates");
+ mGuidedEnrollmentPoints.add(new PointF( 2.00f * px, 0.00f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 0.88f * px, 2.70f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px));
+ mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 2.29f * px, 4.92f * px));
+ mGuidedEnrollmentPoints.add(new PointF( 3.82f * px, 1.78f * px));
+ }
}
boolean shouldShowProgressBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index ed3d5ec..8e4e308 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,6 +16,7 @@
package com.android.systemui.dagger;
+import com.android.keyguard.clock.ClockOptionsProvider;
import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.Dependency;
import com.android.systemui.InitController;
@@ -150,4 +151,9 @@
* Member injection into the supplied argument.
*/
void inject(KeyguardSliceProvider keyguardSliceProvider);
+
+ /**
+ * Member injection into the supplied argument.
+ */
+ void inject(ClockOptionsProvider clockOptionsProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b0067cd..b67db03 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -22,6 +22,7 @@
import androidx.annotation.Nullable;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.clock.ClockModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
@@ -90,6 +91,7 @@
@Module(includes = {
AppOpsModule.class,
AssistModule.class,
+ ClockModule.class,
ControlsModule.class,
DemoModeModule.class,
FalsingModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 553e5a7..1862718 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -73,8 +73,8 @@
import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
-import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
@@ -138,6 +138,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.leak.RotationUtils;
@@ -303,7 +304,8 @@
AudioManager audioManager, IDreamManager iDreamManager,
DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
- ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+ ConnectivityManager connectivityManager,
+ TelephonyListenerManager telephonyListenerManager,
ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
ConfigurationController configurationController, ActivityStarter activityStarter,
KeyguardStateController keyguardStateController, UserManager userManager,
@@ -361,7 +363,7 @@
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
// get notified of phone state changes
- telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ telephonyListenerManager.addServiceStateListener(mPhoneStateListener);
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
mAirplaneModeObserver);
@@ -2049,7 +2051,8 @@
}
};
- PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ private final TelephonyCallback.ServiceStateListener mPhoneStateListener =
+ new TelephonyCallback.ServiceStateListener() {
@Override
public void onServiceStateChanged(ServiceState serviceState) {
if (!mHasTelephony) return;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index de2e7c47..a747edd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -30,6 +30,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -63,8 +64,11 @@
/**
* Dagger Module providing {@link StatusBar}.
*/
-@Module(subcomponents = {KeyguardStatusViewComponent.class,
- KeyguardQsUserSwitchComponent.class, KeyguardUserSwitcherComponent.class},
+@Module(subcomponents = {
+ KeyguardQsUserSwitchComponent.class,
+ KeyguardStatusBarViewComponent.class,
+ KeyguardStatusViewComponent.class,
+ KeyguardUserSwitcherComponent.class},
includes = {FalsingModule.class})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 93ce5a8..5bc1280 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -274,13 +274,17 @@
return tile;
}
boolean isMissedCall = Objects.equals(notification.category, CATEGORY_MISSED_CALL);
- Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(notification);
+ List<Notification.MessagingStyle.Message> messages =
+ getMessagingStyleMessages(notification);
- if (!isMissedCall && message == null) {
+ if (!isMissedCall && ArrayUtils.isEmpty(messages)) {
if (DEBUG) Log.d(TAG, "Notification has no content");
return tile;
}
+ // messages are in chronological order from most recent to least.
+ Notification.MessagingStyle.Message message = messages != null ? messages.get(0) : null;
+ int messagesCount = messages != null ? messages.size() : 0;
// If it's a missed call notification and it doesn't include content, use fallback value,
// otherwise, use notification content.
boolean hasMessageText = message != null && !TextUtils.isEmpty(message.getText());
@@ -294,12 +298,16 @@
.setNotificationCategory(notification.category)
.setNotificationContent(content)
.setNotificationDataUri(dataUri)
+ .setMessagesCount(messagesCount)
.build();
}
- /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */
+ /**
+ * Returns {@link Notification.MessagingStyle.Message}s from the Notification in chronological
+ * order from most recent to least.
+ */
@VisibleForTesting
- public static Notification.MessagingStyle.Message getLastMessagingStyleMessage(
+ public static List<Notification.MessagingStyle.Message> getMessagingStyleMessages(
Notification notification) {
if (notification == null) {
return null;
@@ -312,7 +320,7 @@
Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
sortedMessages.sort(Collections.reverseOrder(
Comparator.comparing(Notification.MessagingStyle.Message::getTimestamp)));
- return sortedMessages.get(0);
+ return sortedMessages;
}
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index ae81ab04..bc196bf 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -58,8 +58,10 @@
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
+import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
@@ -82,6 +84,8 @@
private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8;
private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4;
+ private static final int MESSAGES_COUNT_OVERFLOW = 7;
+
private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
@@ -97,6 +101,9 @@
private int mHeight;
private int mLayoutSize;
+ private Locale mLocale;
+ private NumberFormat mIntegerFormat;
+
PeopleTileViewHelper(Context context, PeopleSpaceTile tile,
int appWidgetId, Bundle options) {
mContext = context;
@@ -354,12 +361,35 @@
views.setViewVisibility(R.id.image, View.GONE);
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
}
+ if (mTile.getMessagesCount() > 1 && mLayoutSize == LAYOUT_MEDIUM) {
+ views.setViewVisibility(R.id.messages_count, View.VISIBLE);
+ views.setTextViewText(R.id.messages_count,
+ getMessagesCountText(mTile.getMessagesCount()));
+ }
// TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and
// subtract 1 from maxLines when present.
views.setViewVisibility(R.id.subtext, View.GONE);
return views;
}
+ // Some messaging apps only include up to 7 messages in their notifications.
+ private String getMessagesCountText(int count) {
+ if (count >= MESSAGES_COUNT_OVERFLOW) {
+ return mContext.getResources().getString(
+ R.string.messages_count_overflow_indicator, MESSAGES_COUNT_OVERFLOW);
+ }
+
+ // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed
+ // non-null, so the first time this is called we will always get the appropriate
+ // NumberFormat, then never regenerate it unless the locale changes on the fly.
+ final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+ if (!curLocale.equals(mLocale)) {
+ mLocale = curLocale;
+ mIntegerFormat = NumberFormat.getIntegerInstance(curLocale);
+ }
+ return mIntegerFormat.format(count);
+ }
+
private RemoteViews createStatusRemoteViews(ConversationStatus status) {
RemoteViews views = getViewForContentLayout();
CharSequence statusText = status.getDescription();
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 4ad685e..776e8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -328,6 +328,7 @@
.setNotificationKey(null)
.setNotificationContent(null)
.setNotificationDataUri(null)
+ .setMessagesCount(0)
// Reset missed calls category.
.setNotificationCategory(null)
.build();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index 54e8a2b..cc5a771 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -118,7 +118,19 @@
QS_USER_DETAIL_CLOSE(426),
@UiEvent(doc = "User switcher QS detail panel more settings pressed")
- QS_USER_MORE_SETTINGS(427);
+ QS_USER_MORE_SETTINGS(427),
+
+ @UiEvent(doc = "The user has added a guest in the detail panel")
+ QS_USER_GUEST_ADD(754),
+
+ @UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user")
+ QS_USER_GUEST_WIPE(755),
+
+ @UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user")
+ QS_USER_GUEST_CONTINUE(756),
+
+ @UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel")
+ QS_USER_GUEST_REMOVE(757);
override fun getId() = _id
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 994da9a..8aa2d09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs;
+import android.app.ActivityManager;
import android.database.ContentObserver;
import android.os.Handler;
@@ -24,27 +25,36 @@
/** Helper for managing a secure setting. **/
public abstract class SecureSetting extends ContentObserver implements Listenable {
- private static final int DEFAULT = 0;
-
- private SecureSettings mSecureSettings;
+ private final SecureSettings mSecureSettings;
private final String mSettingName;
+ private final int mDefaultValue;
private boolean mListening;
private int mUserId;
- private int mObservedValue = DEFAULT;
+ private int mObservedValue;
protected abstract void handleValueChanged(int value, boolean observedChange);
public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName,
int userId) {
+ this(secureSettings, handler, settingName, userId, 0);
+ }
+
+ public SecureSetting(SecureSettings secureSetting, Handler handler, String settingName) {
+ this(secureSetting, handler, settingName, ActivityManager.getCurrentUser());
+ }
+
+ public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName,
+ int userId, int defaultValue) {
super(handler);
mSecureSettings = secureSettings;
mSettingName = settingName;
+ mObservedValue = mDefaultValue = defaultValue;
mUserId = userId;
}
public int getValue() {
- return mSecureSettings.getIntForUser(mSettingName, DEFAULT, mUserId);
+ return mSecureSettings.getIntForUser(mSettingName, mDefaultValue, mUserId);
}
public void setValue(int value) {
@@ -61,7 +71,7 @@
mSecureSettings.getUriFor(mSettingName), false, this, mUserId);
} else {
mSecureSettings.unregisterContentObserver(this);
- mObservedValue = DEFAULT;
+ mObservedValue = mDefaultValue;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 0dc0b30..5afe1c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -34,7 +34,7 @@
import androidx.annotation.VisibleForTesting;
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.R;
@@ -59,7 +59,7 @@
private final ActivityStarter mActivityStarter;
private final Handler mBgHandler;
private final NetworkController mNetworkController;
- private final CarrierTextController mCarrierTextController;
+ private final CarrierTextManager mCarrierTextManager;
private final TextView mNoSimTextView;
private final H mMainHandler;
private final Callback mCallback;
@@ -149,7 +149,7 @@
}
};
- private static class Callback implements CarrierTextController.CarrierTextCallback {
+ private static class Callback implements CarrierTextManager.CarrierTextCallback {
private H mHandler;
Callback(H handler) {
@@ -157,7 +157,7 @@
}
@Override
- public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+ public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
}
}
@@ -165,7 +165,7 @@
private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
@Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
- CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+ CarrierTextManager.Builder carrierTextManagerBuilder, Context context) {
if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
mProviderModel = true;
} else {
@@ -174,7 +174,7 @@
mActivityStarter = activityStarter;
mBgHandler = bgHandler;
mNetworkController = networkController;
- mCarrierTextController = carrierTextControllerBuilder
+ mCarrierTextManager = carrierTextManagerBuilder
.setShowAirplaneMode(false)
.setShowMissingSim(false)
.build();
@@ -192,7 +192,6 @@
mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
mCallback = new Callback(mMainHandler);
-
mCarrierGroups[0] = view.getCarrier1View();
mCarrierGroups[1] = view.getCarrier2View();
mCarrierGroups[2] = view.getCarrier3View();
@@ -243,10 +242,10 @@
if (mNetworkController.hasVoiceCallingFeature()) {
mNetworkController.addCallback(mSignalCallback);
}
- mCarrierTextController.setListening(mCallback);
+ mCarrierTextManager.setListening(mCallback);
} else {
mNetworkController.removeCallback(mSignalCallback);
- mCarrierTextController.setListening(null);
+ mCarrierTextManager.setListening(null);
}
}
@@ -273,7 +272,7 @@
}
@MainThread
- private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+ private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
if (!mMainHandler.getLooper().isCurrentThread()) {
mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
return;
@@ -327,13 +326,13 @@
}
private static class H extends Handler {
- private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo;
+ private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo;
private Runnable mUpdateState;
static final int MSG_UPDATE_CARRIER_INFO = 0;
static final int MSG_UPDATE_STATE = 1;
H(Looper looper,
- Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo,
+ Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo,
Runnable updateState) {
super(looper);
mUpdateCarrierInfo = updateCarrierInfo;
@@ -345,7 +344,7 @@
switch (msg.what) {
case MSG_UPDATE_CARRIER_INFO:
mUpdateCarrierInfo.accept(
- (CarrierTextController.CarrierTextCallbackInfo) msg.obj);
+ (CarrierTextManager.CarrierTextCallbackInfo) msg.obj);
break;
case MSG_UPDATE_STATE:
mUpdateState.run();
@@ -362,13 +361,13 @@
private final Handler mHandler;
private final Looper mLooper;
private final NetworkController mNetworkController;
- private final CarrierTextController.Builder mCarrierTextControllerBuilder;
+ private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
private final Context mContext;
@Inject
public Builder(ActivityStarter activityStarter, @Background Handler handler,
@Main Looper looper, NetworkController networkController,
- CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+ CarrierTextManager.Builder carrierTextControllerBuilder, Context context) {
mActivityStarter = activityStarter;
mHandler = handler;
mLooper = looper;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 01afacf..04d1996 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -267,8 +267,14 @@
@Override
protected void onPause() {
- Log.d(TAG, "onPause finishing=" + isFinishing());
+ Log.d(TAG, "onPause");
super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ Log.d(TAG, "onStop finishing=" + isFinishing());
+ super.onStop();
if (isFinishing()) {
if (mScrollCaptureResponse != null) {
mScrollCaptureResponse.close();
@@ -297,12 +303,6 @@
}
@Override
- protected void onStop() {
- Log.d(TAG, "onStop");
- super.onStop();
- }
-
- @Override
protected void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index c1ae292..6d019d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -25,7 +25,6 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
import static com.android.systemui.screenshot.LogConfig.logTag;
@@ -56,7 +55,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -78,7 +76,6 @@
import android.widget.Toast;
import com.android.internal.app.ChooserActivity;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
@@ -86,7 +83,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.util.DeviceConfigProxy;
import com.google.common.util.concurrent.ListenableFuture;
@@ -193,7 +189,6 @@
private final AccessibilityManager mAccessibilityManager;
private final MediaActionSound mCameraSound;
private final ScrollCaptureClient mScrollCaptureClient;
- private final DeviceConfigProxy mConfigProxy;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
@@ -237,7 +232,6 @@
ScreenshotNotificationsController screenshotNotificationsController,
ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
- DeviceConfigProxy configProxy,
ImageExporter imageExporter,
@Main Executor mainExecutor) {
mScreenshotSmartActions = screenshotSmartActions;
@@ -254,7 +248,6 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mConfigProxy = configProxy;
// Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -525,22 +518,17 @@
// The window is focusable by default
setWindowFocusable(true);
- if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) {
- View decorView = mWindow.getDecorView();
-
- // Wait until this window is attached to request because it is
- // the reference used to locate the target window (below).
- withWindowAttached(() -> {
- mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken());
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- }
- mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY);
- mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor);
- });
- }
+ // Wait until this window is attached to request because it is
+ // the reference used to locate the target window (below).
+ withWindowAttached(() -> {
+ mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
+ if (mLastScrollCaptureRequest != null) {
+ mLastScrollCaptureRequest.cancel(true);
+ }
+ mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY);
+ mLastScrollCaptureRequest.addListener(() ->
+ onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor);
+ });
attachWindow();
mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index ead6d32..e27c1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -189,7 +189,7 @@
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
val zoomOut = blurUtils.ratioOfBlurRadius(blur)
try {
- if (root.isAttachedToWindow) {
+ if (root.isAttachedToWindow && root.windowToken != null) {
wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
} else {
Log.i(TAG, "Won't set zoom. Window not attached $root")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 815cfb3..a3a4014 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -656,11 +656,6 @@
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S;
- if (Notification.DevFlags.shouldBackportSNotifRules(mContext.getContentResolver())) {
- // When back-porting S rules, if an app targets P/Q/R then enforce the new S rule on
- // that notification. If it's before P though, we still want to enforce legacy rules.
- beforeS = beforeP;
- }
int smallHeight;
View expandedView = layout.getExpandedChild();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 3728388..609ca97c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -25,6 +25,7 @@
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import com.android.systemui.R;
@@ -101,6 +102,28 @@
}
};
+ /**
+ * Get the relative start padding of a view relative to this view. This recursively walks up the
+ * hierarchy and does the corresponding measuring.
+ *
+ * @param view the view to get the padding for. The requested view has to be a child of this
+ * notification.
+ * @return the start padding
+ */
+ public int getRelativeStartPadding(View view) {
+ boolean isRtl = isLayoutRtl();
+ int startPadding = 0;
+ while (view.getParent() instanceof ViewGroup) {
+ View parent = (View) view.getParent();
+ startPadding += isRtl ? parent.getWidth() - view.getRight() : view.getLeft();
+ view = parent;
+ if (view == this) {
+ return startPadding;
+ }
+ }
+ return startPadding;
+ }
+
protected Path getClipPath(boolean ignoreTranslation) {
int left;
int top;
@@ -109,15 +132,17 @@
int height;
float topRoundness = mAlwaysRoundBothCorners
? mOutlineRadius : getCurrentBackgroundRadiusTop();
+
if (!mCustomOutline) {
- int translation = mShouldTranslateContents && !ignoreTranslation
- ? (int) getTranslation() : 0;
- int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
- left = Math.max(translation, 0) - halfExtraWidth;
- top = mClipTopAmount + mBackgroundTop;
- right = getWidth() + halfExtraWidth + Math.min(translation, 0);
+ // Extend left/right clip bounds beyond the notification by the
+ // 1) space between the notification and edge of screen
+ // 2) corner radius (so we do not see any rounding as the notification goes off screen)
+ left = (int) (-getRelativeStartPadding(this) - mOutlineRadius);
+ right = (int) (((View) getParent()).getWidth() + mOutlineRadius);
+
// If the top is rounded we want the bottom to be at most at the top roundness, in order
// to avoid the shadow changing when scrolling up.
+ top = mClipTopAmount + mBackgroundTop;
bottom = Math.max(mMinimumHeightForClipping,
Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRoundness)));
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 73e0804..1086d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -69,6 +69,8 @@
private float mContentTranslation;
protected boolean mLastInSection;
protected boolean mFirstInSection;
+ private float mOutlineRadius;
+ private float mParentWidth;
public ExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -79,6 +81,7 @@
private void initDimens() {
mContentShift = getResources().getDimensionPixelSize(
R.dimen.shelf_transform_content_shift);
+ mOutlineRadius = getResources().getDimensionPixelSize(R.dimen.notification_corner_radius);
}
@Override
@@ -150,6 +153,9 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ if (getParent() != null) {
+ mParentWidth = ((View) getParent()).getWidth();
+ }
updateClipping();
}
@@ -436,11 +442,15 @@
protected void updateClipping() {
if (mClipToActualHeight && shouldClipToActualHeight()) {
- int top = getClipTopAmount();
- int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
+ final int top = getClipTopAmount();
+ final int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
- mClipBottomAmount, top), mMinimumHeightForClipping);
- int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
- mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom);
+ // Extend left/right clip bounds beyond the notification by the
+ // 1) space between the notification and edge of screen
+ // 2) corner radius (so we do not see any rounding as the notification goes off screen)
+ final int left = (int) (-getRelativeStartPadding(this) - mOutlineRadius);
+ final int right = (int) (mParentWidth + mOutlineRadius);
+ mClipRect.set(left, top, right, bottom);
setClipBounds(mClipRect);
} else {
setClipBounds(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
new file mode 100644
index 0000000..377fb92
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 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 com.android.keyguard.CarrierTextController;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
+public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
+ private final CarrierTextController mCarrierTextController;
+
+ @Inject
+ public KeyguardStatusBarViewController(
+ KeyguardStatusBarView view, CarrierTextController carrierTextController) {
+ super(view);
+ mCarrierTextController = carrierTextController;
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mCarrierTextController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ }
+
+ @Override
+ protected void onViewDetached() {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 415cfff..d3da0bce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -83,6 +83,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.DejankUtils;
@@ -298,6 +299,7 @@
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+ private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
private final QSDetailDisplayer mQSDetailDisplayer;
private final FeatureFlags mFeatureFlags;
private final ScrimController mScrimController;
@@ -313,6 +315,7 @@
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
+ private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
private ViewGroup mBigClockContainer;
private QS mQs;
private FrameLayout mQsFrame;
@@ -565,6 +568,7 @@
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+ KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
QSDetailDisplayer qsDetailDisplayer,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
@@ -589,6 +593,7 @@
mGroupManager = groupManager;
mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
+ mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
mFeatureFlags = featureFlags;
mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
@@ -693,7 +698,9 @@
}
updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
- userAvatarView, keyguardUserSwitcherView);
+ userAvatarView,
+ mKeyguardStatusBar,
+ keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
R.id.notification_stack_scroller);
@@ -773,13 +780,21 @@
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
- UserAvatarView userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) {
+ UserAvatarView userAvatarView,
+ KeyguardStatusBarView keyguardStatusBarView,
+ KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
+ KeyguardStatusBarViewComponent statusBarViewComponent =
+ mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView);
+ mKeyguarStatusBarViewController =
+ statusBarViewComponent.getKeyguardStatusBarViewController();
+ mKeyguarStatusBarViewController.init();
+
// Re-associate the clock container with the keyguard clock switch.
KeyguardClockSwitchController keyguardClockSwitchController =
statusViewComponent.getKeyguardClockSwitchController();
@@ -919,7 +934,8 @@
showKeyguardUserSwitcher /* enabled */);
mBigClockContainer.removeAllViews();
- updateViewControllers(keyguardStatusView, userAvatarView, keyguardUserSwitcherView);
+ updateViewControllers(
+ keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
// Update keyguard bottom area
index = mView.indexOfChild(mKeyguardBottomArea);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 525f220..94edd1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -187,7 +188,7 @@
boolean removedByUser,
int reason) {
StatusBarNotificationPresenter.this.onNotificationRemoved(
- entry.getKey(), entry.getSbn());
+ entry.getKey(), entry.getSbn(), reason);
if (removedByUser) {
maybeEndAmbientPulse();
}
@@ -301,13 +302,14 @@
mNotificationPanel.updateNotificationViews(reason);
}
- public void onNotificationRemoved(String key, StatusBarNotification old) {
+ private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
&& !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE) {
+ if (mStatusBarStateController.getState() == StatusBarState.SHADE
+ && reason != NotificationListenerService.REASON_CLICK) {
mCommandQueue.animateCollapsePanels();
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 8a86021..cfaeb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -22,7 +22,6 @@
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -44,11 +43,11 @@
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.CellSignalStrength;
-import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
@@ -74,6 +73,7 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.telephony.TelephonyListenerManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -108,6 +108,7 @@
private final Context mContext;
private final TelephonyManager mPhone;
+ private final TelephonyListenerManager mTelephonyListenerManager;
private final WifiManager mWifiManager;
private final ConnectivityManager mConnectivityManager;
private final SubscriptionManager mSubscriptionManager;
@@ -121,7 +122,7 @@
private final boolean mProviderModel;
private Config mConfig;
- private PhoneStateListener mPhoneStateListener;
+ private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
// Subcontrollers.
@@ -201,12 +202,14 @@
BroadcastDispatcher broadcastDispatcher,
ConnectivityManager connectivityManager,
TelephonyManager telephonyManager,
+ TelephonyListenerManager telephonyListenerManager,
@Nullable WifiManager wifiManager,
NetworkScoreManager networkScoreManager,
AccessPointControllerImpl accessPointController,
DemoModeController demoModeController) {
this(context, connectivityManager,
telephonyManager,
+ telephonyListenerManager,
wifiManager,
networkScoreManager,
SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
@@ -222,7 +225,9 @@
@VisibleForTesting
NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
- TelephonyManager telephonyManager, WifiManager wifiManager,
+ TelephonyManager telephonyManager,
+ TelephonyListenerManager telephonyListenerManager,
+ WifiManager wifiManager,
NetworkScoreManager networkScoreManager,
SubscriptionManager subManager, Config config, Looper bgLooper,
CallbackHandler callbackHandler,
@@ -233,6 +238,7 @@
BroadcastDispatcher broadcastDispatcher,
DemoModeController demoModeController) {
mContext = context;
+ mTelephonyListenerManager = telephonyListenerManager;
mConfig = config;
mReceiverHandler = new Handler(bgLooper);
mCallbackHandler = callbackHandler;
@@ -372,23 +378,20 @@
// exclusively for status bar icons.
mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler);
// Register the listener on our bg looper
- mPhoneStateListener = new PhoneStateListener(mReceiverHandler::post) {
- @Override
- public void onActiveDataSubscriptionIdChanged(int subId) {
- // For data switching from A to B, we assume B is validated for up to 2 seconds iff:
- // 1) A and B are in the same subscription group e.g. CBRS data switch. And
- // 2) A was validated before the switch.
- // This is to provide smooth transition for UI without showing cross during data
- // switch.
- if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
- if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
- mForceCellularValidated = true;
- mReceiverHandler.removeCallbacks(mClearForceValidated);
- mReceiverHandler.postDelayed(mClearForceValidated, 2000);
- }
- mActiveMobileDataSubscription = subId;
- doUpdateMobileControllers();
+ mPhoneStateListener = subId -> {
+ // For data switching from A to B, we assume B is validated for up to 2 seconds iff:
+ // 1) A and B are in the same subscription group e.g. CBRS data switch. And
+ // 2) A was validated before the switch.
+ // This is to provide smooth transition for UI without showing cross during data
+ // switch.
+ if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
+ if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
+ mForceCellularValidated = true;
+ mReceiverHandler.removeCallbacks(mClearForceValidated);
+ mReceiverHandler.postDelayed(mClearForceValidated, 2000);
}
+ mActiveMobileDataSubscription = subId;
+ doUpdateMobileControllers();
};
mDemoModeController.addCallback(this);
@@ -428,7 +431,7 @@
mSubscriptionListener = new SubListener();
}
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
- mPhone.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
// broadcasts
IntentFilter filter = new IntentFilter();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index d4029e64..0da441d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -42,8 +42,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
+import android.telephony.TelephonyCallback;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -69,6 +68,7 @@
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.CreateUserActivity;
import java.io.FileDescriptor;
@@ -99,12 +99,12 @@
protected final Context mContext;
protected final UserManager mUserManager;
private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
- private final GuestResumeSessionReceiver mGuestResumeSessionReceiver
- = new GuestResumeSessionReceiver();
+ private final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
private final KeyguardStateController mKeyguardStateController;
protected final Handler mHandler;
private final ActivityStarter mActivityStarter;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final TelephonyListenerManager mTelephonyListenerManager;
private final IActivityTaskManager mActivityTaskManager;
private ArrayList<UserRecord> mUsers = new ArrayList<>();
@@ -127,11 +127,14 @@
public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@Main Handler handler, ActivityStarter activityStarter,
BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger,
+ TelephonyListenerManager telephonyListenerManager,
IActivityTaskManager activityTaskManager) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mTelephonyListenerManager = telephonyListenerManager;
mActivityTaskManager = activityTaskManager;
mUiEventLogger = uiEventLogger;
+ mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(mUiEventLogger);
mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger);
if (!UserManager.isGuestUserEphemeral()) {
mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
@@ -388,6 +391,7 @@
// haven't reloaded the user list yet.
return;
}
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
id = guest.id;
} else if (record.isAddUser) {
showAddUserDialog();
@@ -458,18 +462,15 @@
}
private void listenForCallState() {
- final TelephonyManager tele =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- if (tele != null) {
- tele.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- }
+ mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
}
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ private final TelephonyCallback.CallStateListener mPhoneStateListener =
+ new TelephonyCallback.CallStateListener() {
private int mCallState;
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
+ public void onCallStateChanged(int state) {
if (mCallState == state) return;
if (DEBUG) Log.v(TAG, "Call state changed: " + state);
mCallState = state;
@@ -891,6 +892,7 @@
if (which == BUTTON_NEGATIVE) {
cancel();
} else {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
dismiss();
exitGuest(mGuestId, mTargetId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
new file mode 100644
index 0000000..3bc2632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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.telephony;
+
+import android.telephony.ServiceState;
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+class TelephonyCallback extends android.telephony.TelephonyCallback
+ implements ActiveDataSubscriptionIdListener, CallStateListener, ServiceStateListener {
+
+ private final List<ActiveDataSubscriptionIdListener> mActiveDataSubscriptionIdListeners =
+ new ArrayList<>();
+ private final List<CallStateListener> mCallStateListeners = new ArrayList<>();
+ private final List<ServiceStateListener> mServiceStateListeners = new ArrayList<>();
+
+ @Inject
+ TelephonyCallback() {
+ }
+
+ boolean hasAnyListeners() {
+ return !mActiveDataSubscriptionIdListeners.isEmpty()
+ || !mCallStateListeners.isEmpty()
+ || !mServiceStateListeners.isEmpty();
+ }
+
+ @Override
+ public void onActiveDataSubscriptionIdChanged(int subId) {
+ mActiveDataSubscriptionIdListeners.forEach(listener -> {
+ listener.onActiveDataSubscriptionIdChanged(subId);
+ });
+ }
+
+ void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+ mActiveDataSubscriptionIdListeners.add(listener);
+ }
+
+ void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+ mActiveDataSubscriptionIdListeners.remove(listener);
+ }
+
+ @Override
+ public void onCallStateChanged(int state) {
+ mCallStateListeners.forEach(listener -> {
+ listener.onCallStateChanged(state);
+ });
+ }
+
+ void addCallStateListener(CallStateListener listener) {
+ mCallStateListeners.add(listener);
+ }
+
+ void removeCallStateListener(CallStateListener listener) {
+ mCallStateListeners.remove(listener);
+ }
+
+ @Override
+ public void onServiceStateChanged(@NonNull ServiceState serviceState) {
+ mServiceStateListeners.forEach(listener -> {
+ listener.onServiceStateChanged(serviceState);
+ });
+ }
+
+ void addServiceStateListener(ServiceStateListener listener) {
+ mServiceStateListeners.add(listener);
+ }
+
+ void removeServiceStateListener(ServiceStateListener listener) {
+ mServiceStateListeners.remove(listener);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
new file mode 100644
index 0000000..4e1acca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 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.telephony;
+
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+import android.telephony.TelephonyManager;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Wrapper around {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ *
+ * The TelephonyManager complains if too many places in code register a listener. This class
+ * encapsulates SystemUI's usage of this function, reducing it down to a single listener.
+ *
+ * See also
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, android.telephony.TelephonyCallback)}
+ */
+@SysUISingleton
+public class TelephonyListenerManager {
+ private final TelephonyManager mTelephonyManager;
+ private final Executor mExecutor;
+ private final TelephonyCallback mTelephonyCallback;
+
+ private boolean mListening = false;
+
+ @Inject
+ public TelephonyListenerManager(TelephonyManager telephonyManager, @Main Executor executor,
+ TelephonyCallback telephonyCallback) {
+ mTelephonyManager = telephonyManager;
+ mExecutor = executor;
+ mTelephonyCallback = telephonyCallback;
+ }
+
+ /** */
+ public void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+ mTelephonyCallback.addActiveDataSubscriptionIdListener(listener);
+ updateListening();
+ }
+
+ /** */
+ public void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
+ mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener);
+ updateListening();
+ }
+
+ /** */
+ public void addCallStateListener(CallStateListener listener) {
+ mTelephonyCallback.addCallStateListener(listener);
+ updateListening();
+ }
+
+ /** */
+ public void removeCallStateListener(CallStateListener listener) {
+ mTelephonyCallback.removeCallStateListener(listener);
+ updateListening();
+ }
+
+ /** */
+ public void addServiceStateListener(ServiceStateListener listener) {
+ mTelephonyCallback.addServiceStateListener(listener);
+ updateListening();
+ }
+
+ /** */
+ public void removeServiceStateListener(ServiceStateListener listener) {
+ mTelephonyCallback.removeServiceStateListener(listener);
+ updateListening();
+ }
+
+
+ private void updateListening() {
+ if (!mListening && mTelephonyCallback.hasAnyListeners()) {
+ mListening = true;
+ mTelephonyManager.registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ } else if (mListening && !mTelephonyCallback.hasAnyListeners()) {
+ mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
+ mListening = false;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
similarity index 73%
rename from packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 6f2c0af..c2c7dde 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -21,12 +21,13 @@
import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -40,10 +41,6 @@
import android.content.pm.PackageManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
import android.provider.Settings;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -51,13 +48,14 @@
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
import android.text.TextUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -73,8 +71,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class CarrierTextControllerTest extends SysuiTestCase {
+public class CarrierTextManagerTest extends SysuiTestCase {
private static final CharSequence SEPARATOR = " \u2014 ";
private static final CharSequence INVALID_CARD_TEXT = "Invalid card";
@@ -95,7 +92,9 @@
@Mock
private WifiManager mWifiManager;
@Mock
- private CarrierTextController.CarrierTextCallback mCarrierTextCallback;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock
+ private CarrierTextManager.CarrierTextCallback mCarrierTextCallback;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
@@ -103,24 +102,25 @@
@Mock
private TelephonyManager mTelephonyManager;
@Mock
+ private TelephonyListenerManager mTelephonyListenerManager;
+ private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+ private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
+ @Mock
private SubscriptionManager mSubscriptionManager;
- private CarrierTextController.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
+ private CarrierTextManager.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
- private CarrierTextController mCarrierTextController;
- private TestableLooper mTestableLooper;
+ private CarrierTextManager mCarrierTextManager;
private Void checkMainThread(InvocationOnMock inv) {
- Looper mainLooper = Dependency.get(Dependency.MAIN_HANDLER).getLooper();
- if (!mainLooper.isCurrentThread()) {
- fail("This call should be done from the main thread");
- }
+ assertThat(mMainExecutor.isExecuting()).isTrue();
+ assertThat(mBgExecutor.isExecuting()).isFalse();
return null;
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
mContext.addMockSystemService(WifiManager.class, mWifiManager);
mContext.addMockSystemService(PackageManager.class, mPackageManager);
@@ -132,9 +132,6 @@
mContext.getOrCreateTestableResources().addOverride(
R.string.airplane_mode, AIRPLANE_MODE_TEXT);
mDependency.injectMockDependency(WakefulnessLifecycle.class);
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
- new Handler(mTestableLooper.getLooper()));
- mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
@@ -142,35 +139,30 @@
doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
.removeCallback(any(KeyguardUpdateMonitorCallback.class));
- mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
+ mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("",
new CharSequence[]{}, false, new int[]{});
when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
- mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true);
+ mCarrierTextManager = new CarrierTextManager.Builder(
+ mContext, mContext.getResources(), mWifiManager,
+ mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
+ mBgExecutor, mKeyguardUpdateMonitor)
+ .setShowAirplaneMode(true)
+ .setShowMissingSim(true)
+ .build();
+
// This should not start listening on any of the real dependencies but will test that
// callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread
- mCarrierTextController.setListening(mCarrierTextCallback);
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.setListening(mCarrierTextCallback);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
}
@Test
public void testKeyguardUpdateMonitorCalledInMainThread() throws Exception {
- // This test will run on the main looper (which is not the same as the looper set as MAIN
- // for CarrierTextCallback. This will fail if calls to mKeyguardUpdateMonitor are not done
- // through the looper set in the set up
- HandlerThread thread = new HandlerThread("testThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- TestableLooper testableLooper = new TestableLooper(thread.getLooper());
- Handler h = new Handler(testableLooper.getLooper());
- h.post(() -> {
- mCarrierTextController.setListening(null);
- mCarrierTextController.setListening(mCarrierTextCallback);
- });
- testableLooper.processAllMessages();
- mTestableLooper.processAllMessages();
- thread.quitSafely();
+ mCarrierTextManager.setListening(null);
+ mCarrierTextManager.setListening(mCarrierTextCallback);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
}
@Test
@@ -183,13 +175,13 @@
when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- mCarrierTextController.updateCarrierText();
+ mCarrierTextManager.updateCarrierText();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mTestableLooper.processAllMessages();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
}
@@ -205,14 +197,14 @@
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- mCarrierTextController.mCallback.onSimStateChanged(3, 1,
+ mCarrierTextManager.mCallback.onSimStateChanged(3, 1,
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mTestableLooper.processAllMessages();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals("TEST_CARRIER" + SEPARATOR + INVALID_CARD_TEXT, captor.getValue().carrierText);
// There's only one subscription in the list
@@ -223,8 +215,8 @@
reset(mCarrierTextCallback);
when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
// Update carrier text. It should ignore error state of subId 3 in inactive slotId.
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals("TEST_CARRIER", captor.getValue().carrierText);
}
@@ -237,9 +229,9 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
// This should not produce an out of bounds error, even though there are no subscriptions
- mCarrierTextController.mCallback.onSimStateChanged(0, -3,
+ mCarrierTextManager.mCallback.onSimStateChanged(0, -3,
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
- mCarrierTextController.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
+ mCarrierTextManager.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
}
@@ -257,23 +249,23 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
// This should not produce an out of bounds error, even though there are no subscriptions
- mCarrierTextController.mCallback.onSimStateChanged(0, 1,
+ mCarrierTextManager.mCallback.onSimStateChanged(0, 1,
TelephonyManager.SIM_STATE_CARD_IO_ERROR);
- mTestableLooper.processAllMessages();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(
- any(CarrierTextController.CarrierTextCallbackInfo.class));
+ any(CarrierTextManager.CarrierTextCallbackInfo.class));
}
@Test
public void testCallback() {
reset(mCarrierTextCallback);
- mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals(mCarrierTextCallbackInfo, captor.getValue());
}
@@ -282,11 +274,11 @@
public void testNullingCallback() {
reset(mCarrierTextCallback);
- mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
- mCarrierTextController.setListening(null);
+ mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
+ mCarrierTextManager.setListening(null);
// This shouldn't produce NPE
- mTestableLooper.processAllMessages();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(any());
}
@@ -301,15 +293,15 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
- CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
assertEquals(1, info.listOfCarriers.length);
assertEquals(TEST_CARRIER, info.listOfCarriers[0]);
assertEquals(1, info.subscriptionIds.length);
@@ -326,15 +318,15 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
- CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
assertEquals(1, info.listOfCarriers.length);
assertTrue(info.listOfCarriers[0].toString().contains(TEST_CARRIER));
assertEquals(1, info.subscriptionIds.length);
@@ -346,17 +338,17 @@
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION_NULL);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
- TelephonyManager.SIM_STATE_READY);
+ TelephonyManager.SIM_STATE_READY);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText,
@@ -380,12 +372,12 @@
when(ss.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertFalse("No SIM should be available", captor.getValue().anySimReady);
@@ -407,15 +399,15 @@
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
new ArrayList<>());
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
- CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
assertEquals(0, info.listOfCarriers.length);
assertEquals(0, info.subscriptionIds.length);
@@ -428,17 +420,17 @@
list.add(TEST_SUBSCRIPTION);
list.add(TEST_SUBSCRIPTION);
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
- TelephonyManager.SIM_STATE_READY);
+ TelephonyManager.SIM_STATE_READY);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
@@ -458,12 +450,12 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals(TEST_CARRIER,
@@ -483,12 +475,12 @@
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals(TEST_CARRIER,
@@ -509,12 +501,12 @@
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
- ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
ArgumentCaptor.forClass(
- CarrierTextController.CarrierTextCallbackInfo.class);
+ CarrierTextManager.CarrierTextCallbackInfo.class);
- mCarrierTextController.updateCarrierText();
- mTestableLooper.processAllMessages();
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 0cf343c..90f7fda 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -72,6 +72,8 @@
@Mock
private LatencyTracker mLatencyTracker;
private final FalsingCollector mFalsingCollector = new FalsingCollectorFake();
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
@@ -87,7 +89,8 @@
.thenReturn(mKeyguardMessageArea);
mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector) {
+ mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
+ mEmergencyButtonController) {
@Override
void resetState() {
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 826be2b..4beec57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -54,11 +54,11 @@
public class KeyguardDisplayManagerTest extends SysuiTestCase {
@Mock
+ private NavigationBarController mNavigationBarController;
+ @Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-
@Mock
private DisplayManager mDisplayManager;
-
@Mock
private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
@@ -76,9 +76,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
- mDependency.injectMockDependency(NavigationBarController.class);
- mManager = spy(new KeyguardDisplayManager(mContext, mKeyguardStatusViewComponentFactory,
- mBackgroundExecutor));
+ mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
+ mKeyguardStatusViewComponentFactory, mBackgroundExecutor));
doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index fc93ded..bb71bed8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -52,6 +52,8 @@
private lateinit var mLatencyTracker: LatencyTracker
private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
@Mock
+ private lateinit var mEmergencyButtonController: EmergencyButtonController
+ @Mock
private lateinit
var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
@Mock
@@ -75,7 +77,8 @@
.thenReturn(mKeyguardMessageAreaController)
mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mLatencyTracker, mFalsingCollector, mKeyguardMessageAreaControllerFactory)
+ mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
+ mKeyguardMessageAreaControllerFactory)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 33a0dcd0..9597cab 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -68,6 +68,8 @@
private LatencyTracker mLatencyTracker;
@Mock
private LiftToActivateListener mLiftToactivateListener;
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
private SingleTapClassifier mSingleTapClassifier;
@@ -97,7 +99,7 @@
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
- mFalsingCollector) {
+ mEmergencyButtonController, mFalsingCollector) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 096ce0f..084c0b4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -94,6 +94,8 @@
private KeyguardMessageArea mKeyguardMessageArea;
@Mock
private ConfigurationController mConfigurationController;
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
private KeyguardPasswordViewController mKeyguardPasswordViewController;
@@ -112,11 +114,11 @@
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
SecurityMode.Password, mLockPatternUtils, null,
- mKeyguardMessageAreaControllerFactory, null, null, null, mock(Resources.class),
- null);
+ mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
+ null, mock(Resources.class), null);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
- mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+ mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 3b7f4b8..9296d3d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -59,6 +59,10 @@
@Mock
private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
@Mock
+ private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
+ @Mock
private KeyguardInputViewController mKeyguardInputViewController;
@Mock
private KeyguardInputView mInputView;
@@ -76,9 +80,12 @@
any(KeyguardSecurityCallback.class)))
.thenReturn(mKeyguardInputViewController);
when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+ when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
+ .thenReturn(mEmergencyButtonController);
mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
- mLayoutInflater, mKeyguardSecurityViewControllerFactory);
+ mLayoutInflater, mKeyguardSecurityViewControllerFactory,
+ mEmergencyButtonControllerFactory);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 52e2016..160dae5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -88,6 +88,7 @@
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.RingerModeTracker;
import org.junit.After;
@@ -163,6 +164,8 @@
@Mock
private AuthController mAuthController;
@Mock
+ private TelephonyListenerManager mTelephonyListenerManager;
+ @Mock
private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
@@ -883,7 +886,7 @@
mBroadcastDispatcher, mDumpManager,
mRingerModeTracker, mBackgroundExecutor,
mStatusBarStateController, mLockPatternUtils,
- mAuthController, mFeatureFlags);
+ mAuthController, mTelephonyListenerManager, mFeatureFlags);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
new file mode 100644
index 0000000..02ba304
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 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.biometrics
+
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.policy.ConfigurationController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AuthRippleControllerTest : SysuiTestCase() {
+ private lateinit var controller: AuthRippleController
+ @Mock private lateinit var commandRegistry: CommandRegistry
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var rippleView: AuthRippleView
+ @Mock private lateinit var viewHost: ViewGroup
+
+ @Before
+
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ controller = AuthRippleController(
+ commandRegistry, configurationController, context, keyguardUpdateMonitor)
+ controller.rippleView = rippleView // Replace the real ripple view with a mock instance
+ controller.setViewHost(viewHost)
+ }
+
+ @Test
+ fun testAddRippleView() {
+ val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture())
+
+ // Fake attach to window
+ listenerCaptor.value.onViewAttachedToWindow(viewHost)
+ verify(viewHost).addView(rippleView)
+ }
+
+ @Test
+ fun testTriggerRipple() {
+ // Fake attach to window
+ val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture())
+ listenerCaptor.value.onViewAttachedToWindow(viewHost)
+
+ val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+ captor.value.onBiometricAuthenticated(
+ 0 /* userId */,
+ BiometricSourceType.FACE /* type */,
+ false /* isStrongBiometric */)
+ verify(rippleView, never()).startRipple()
+
+ captor.value.onBiometricAuthenticated(
+ 0 /* userId */,
+ BiometricSourceType.FINGERPRINT /* type */,
+ false /* isStrongBiometric */)
+ verify(rippleView).startRipple()
+ }
+
+ @Test
+ fun testUpdateRippleColor() {
+ val captor = ArgumentCaptor
+ .forClass(ConfigurationController.ConfigurationListener::class.java)
+ verify(configurationController).addCallback(captor.capture())
+
+ reset(rippleView)
+ captor.value.onThemeChanged()
+ verify(rippleView).setColor(ArgumentMatchers.anyInt())
+
+ reset(rippleView)
+ captor.value.onUiModeChanged()
+ verify(rippleView).setColor(ArgumentMatchers.anyInt())
+ }
+
+ @Test
+ fun testForwardsSensorLocation() {
+ controller.setSensorLocation(5f, 5f)
+ verify(rippleView).setSensorLocation(5f, 5f)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 3f1a927..d3694dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -98,6 +98,8 @@
@Mock
private DumpManager mDumpManager;
@Mock
+ private AuthRippleController mAuthRippleController;
+ @Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
private FakeExecutor mFgExecutor;
@@ -148,7 +150,8 @@
mFgExecutor,
mStatusBar,
mStatusBarKeyguardViewManager,
- mDumpManager);
+ mDumpManager,
+ mAuthRippleController);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index eedf099..8add930 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -43,7 +43,6 @@
import android.os.RemoteException;
import android.os.UserManager;
import android.service.dreams.IDreamManager;
-import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.IWindowManager;
@@ -75,6 +74,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -106,7 +106,7 @@
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private ConnectivityManager mConnectivityManager;
- @Mock private TelephonyManager mTelephonyManager;
+ @Mock private TelephonyListenerManager mTelephonyListenerManager;
@Mock private ContentResolver mContentResolver;
@Mock private Resources mResources;
@Mock private ConfigurationController mConfigurationController;
@@ -167,7 +167,7 @@
mLockPatternUtils,
mBroadcastDispatcher,
mConnectivityManager,
- mTelephonyManager,
+ mTelephonyListenerManager,
mContentResolver,
null,
mResources,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 1c7a84a..1f4dffa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -65,6 +65,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.util.ArrayUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.widget.PeopleTileKey;
@@ -119,6 +120,7 @@
.setNotificationKey(NOTIFICATION_KEY)
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
+ .setMessagesCount(1)
.build();
private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
@@ -318,7 +320,7 @@
}
@Test
- public void testGetLastMessagingStyleMessageNoMessage() {
+ public void testGetMessagingStyleMessagesNoMessage() {
Notification notification = new Notification.Builder(mContext, "test")
.setContentTitle("TEST_TITLE")
.setContentText("TEST_TEXT")
@@ -328,22 +330,23 @@
.setNotification(notification)
.build();
- Notification.MessagingStyle.Message lastMessage =
- PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification());
+ List<Notification.MessagingStyle.Message> messages =
+ PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification());
- assertThat(lastMessage).isNull();
+ assertThat(ArrayUtils.isEmpty(messages)).isTrue();
}
@Test
- public void testGetLastMessagingStyleMessage() {
+ public void testGetMessagingStyleMessages() {
StatusBarNotification sbn = new SbnBuilder()
.setNotification(mNotification1)
.build();
- Notification.MessagingStyle.Message lastMessage =
- PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification());
+ List<Notification.MessagingStyle.Message> messages =
+ PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification());
- assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+ assertThat(messages.size()).isEqualTo(3);
+ assertThat(messages.get(0).getText().toString()).isEqualTo(NOTIFICATION_TEXT_2);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index 39bf060..c2e0d6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -467,6 +467,9 @@
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.person_icon).getVisibility());
+ // Has a single message, no count shown.
+ assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility());
+
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
@@ -492,6 +495,36 @@
}
@Test
+ public void testCreateRemoteViewsWithNotificationTemplateTwoMessages() {
+ PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+ .setNotificationDataUri(null)
+ .setStatuses(Arrays.asList(GAME_STATUS,
+ NEW_STORY_WITH_AVAILABILITY))
+ .setMessagesCount(2).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ TextView subtext = (TextView) result.findViewById(R.id.subtext);
+ assertEquals(View.GONE, subtext.getVisibility());
+ // Has availability.
+ View availability = result.findViewById(R.id.availability);
+ assertEquals(View.VISIBLE, availability.getVisibility());
+ // Has person icon.
+ View personIcon = result.findViewById(R.id.person_icon);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ // Has notification content.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+
+ // Has 2 messages, show count.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
+ }
+
+
+ @Test
public void testGetBackgroundTextFromMessageNoPunctuation() {
String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
index 418fa61..6af8402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
@@ -41,6 +41,7 @@
private const val TEST_SETTING = "setting"
private const val USER = 0
private const val OTHER_USER = 1
+ private const val DEFAULT_VALUE = 1
private val FAIL_CALLBACK: Callback = { _, _ -> fail("Callback should not be called") }
}
@@ -59,7 +60,8 @@
secureSettings,
Handler(testableLooper.looper),
TEST_SETTING,
- USER
+ USER,
+ DEFAULT_VALUE
) {
override fun handleValueChanged(value: Int, observedChange: Boolean) {
callback(value, observedChange)
@@ -150,4 +152,14 @@
assertThat(changed).isTrue()
}
+
+ @Test
+ fun testDefaultValue() {
+ // Check default value before listening
+ assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+
+ // Check default value if setting is not set
+ setting.isListening = true
+ assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 5a1bd5f..59a5f03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -33,7 +33,7 @@
import androidx.test.filters.SmallTest;
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
@@ -55,7 +55,7 @@
private QSCarrierGroupController mQSCarrierGroupController;
private NetworkController.SignalCallback mSignalCallback;
- private CarrierTextController.CarrierTextCallback mCallback;
+ private CarrierTextManager.CarrierTextCallback mCallback;
@Mock
private QSCarrierGroup mQSCarrierGroup;
@Mock
@@ -63,9 +63,9 @@
@Mock
private NetworkController mNetworkController;
@Mock
- private CarrierTextController.Builder mCarrierTextControllerBuilder;
+ private CarrierTextManager.Builder mCarrierTextControllerBuilder;
@Mock
- private CarrierTextController mCarrierTextController;
+ private CarrierTextManager mCarrierTextManager;
private TestableLooper mTestableLooper;
@Before
@@ -84,11 +84,11 @@
.thenReturn(mCarrierTextControllerBuilder);
when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean()))
.thenReturn(mCarrierTextControllerBuilder);
- when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController);
+ when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager);
doAnswer(invocation -> mCallback = invocation.getArgument(0))
- .when(mCarrierTextController)
- .setListening(any(CarrierTextController.CarrierTextCallback.class));
+ .when(mCarrierTextManager)
+ .setListening(any(CarrierTextManager.CarrierTextCallback.class));
when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class));
@@ -114,8 +114,8 @@
(Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
// listOfCarriers length 1, subscriptionIds length 1, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c1 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c1 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
@@ -123,8 +123,8 @@
mCallback.updateCarrierInfo(c1);
// listOfCarriers length 1, subscriptionIds length 1, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c2 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c2 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
@@ -132,8 +132,8 @@
mCallback.updateCarrierInfo(c2);
// listOfCarriers length 2, subscriptionIds length 2, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c3 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c3 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
@@ -141,8 +141,8 @@
mCallback.updateCarrierInfo(c3);
// listOfCarriers length 2, subscriptionIds length 2, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c4 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c4 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
@@ -160,8 +160,8 @@
(Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
// listOfCarriers length 2, subscriptionIds length 1, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c1 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c1 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
false,
@@ -169,8 +169,8 @@
mCallback.updateCarrierInfo(c1);
// listOfCarriers length 2, subscriptionIds length 1, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c2 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c2 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
@@ -178,8 +178,8 @@
mCallback.updateCarrierInfo(c2);
// listOfCarriers length 1, subscriptionIds length 2, anySims false
- CarrierTextController.CarrierTextCallbackInfo
- c3 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c3 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
false,
@@ -187,8 +187,8 @@
mCallback.updateCarrierInfo(c3);
// listOfCarriers length 1, subscriptionIds length 2, anySims true
- CarrierTextController.CarrierTextCallbackInfo
- c4 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c4 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
@@ -204,8 +204,8 @@
when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenReturn(
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
- CarrierTextController.CarrierTextCallbackInfo
- c4 = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ c4 = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{"", ""},
true,
@@ -225,8 +225,8 @@
@Test
public void testNoEmptyVisibleView_airplaneMode() {
- CarrierTextController.CarrierTextCallbackInfo
- info = new CarrierTextController.CarrierTextCallbackInfo(
+ CarrierTextManager.CarrierTextCallbackInfo
+ info = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
index 410d9de..7fe178c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -57,13 +58,17 @@
public int availableBottom = Integer.MAX_VALUE;
// If true, return an empty rect any time a partial result would have been returned.
public boolean emptyInsteadOfPartial = false;
+ private int mPreviousTopRequested = 0;
@Override
public ListenableFuture<ScrollCaptureClient.CaptureResult> requestTile(int top) {
+ // Ensure we don't request a tile more than a tile away.
+ assertTrue(Math.abs(top - mPreviousTopRequested) <= getTileHeight());
+ mPreviousTopRequested = top;
Rect requested = new Rect(0, top, getPageWidth(), top + getTileHeight());
Rect fullContent = new Rect(0, availableTop, getPageWidth(), availableBottom);
Rect captured = new Rect(requested);
- captured.intersect(fullContent);
+ assertTrue(captured.intersect(fullContent));
if (emptyInsteadOfPartial && captured.height() != getTileHeight()) {
captured = new Rect();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0bf1ac3..e65db5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar
import android.app.WallpaperManager
+import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
@@ -64,6 +65,7 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var root: View
@Mock private lateinit var viewRootImpl: ViewRootImpl
+ @Mock private lateinit var windowToken: IBinder
@Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
@@ -80,6 +82,7 @@
@Before
fun setup() {
`when`(root.viewRootImpl).thenReturn(viewRootImpl)
+ `when`(root.windowToken).thenReturn(windowToken)
`when`(root.isAttachedToWindow).thenReturn(true)
`when`(statusBarStateController.state).then { statusBarState }
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 91f3611..6a5e6e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -66,6 +66,7 @@
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.R;
@@ -206,10 +207,16 @@
@Mock
private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
@Mock
+ private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
+ @Mock
+ private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
+ @Mock
private KeyguardClockSwitchController mKeyguardClockSwitchController;
@Mock
private KeyguardStatusViewController mKeyguardStatusViewController;
@Mock
+ private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
+ @Mock
private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock
private AuthController mAuthController;
@@ -296,6 +303,10 @@
.thenReturn(mKeyguardClockSwitchController);
when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
.thenReturn(mKeyguardStatusViewController);
+ when(mKeyguardStatusBarViewComponentFactory.build(any()))
+ .thenReturn(mKeyguardStatusBarViewComponent);
+ when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
+ .thenReturn(mKeyguardStatusBarViewController);
mNotificationPanelViewController = new NotificationPanelViewController(mView,
mResources,
@@ -314,6 +325,7 @@
mKeyguardStatusViewComponentFactory,
mKeyguardQsUserSwitchComponentFactory,
mKeyguardUserSwitcherComponentFactory,
+ mKeyguardStatusBarViewComponentFactory,
mQSDetailDisplayer,
mGroupManager,
mNotificationAreaController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 8f36415..ef33172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -76,6 +76,7 @@
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.telephony.TelephonyListenerManager;
import org.junit.After;
import org.junit.Before;
@@ -113,6 +114,7 @@
protected NetworkScoreManager mMockNsm;
protected SubscriptionManager mMockSm;
protected TelephonyManager mMockTm;
+ protected TelephonyListenerManager mTelephonyListenerManager;
protected BroadcastDispatcher mMockBd;
protected Config mConfig;
protected CallbackHandler mCallbackHandler;
@@ -164,6 +166,7 @@
mDemoModeController = mock(DemoModeController.class);
mMockWm = mock(WifiManager.class);
mMockTm = mock(TelephonyManager.class);
+ mTelephonyListenerManager = mock(TelephonyListenerManager.class);
mMockSm = mock(SubscriptionManager.class);
mMockCm = mock(ConnectivityManager.class);
mMockBd = mock(BroadcastDispatcher.class);
@@ -213,6 +216,7 @@
mNetworkController = new NetworkControllerImpl(mContext,
mMockCm,
mMockTm,
+ mTelephonyListenerManager,
mMockWm,
mMockNsm,
mMockSm,
@@ -285,7 +289,8 @@
protected NetworkControllerImpl setUpNoMobileData() {
when(mMockTm.isDataCapable()).thenReturn(false);
NetworkControllerImpl networkControllerNoMobile =
- new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm,
+ new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager,
+ mMockWm, mMockNsm, mMockSm,
mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
mock(DataUsageController.class), mMockSubDefaults,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index b108dd8..f4ad819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -106,7 +106,8 @@
public void test4gDataIcon() {
// Switch to showing 4g icon and re-initialize the NetworkController.
mConfig.show4gForLte = true;
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+ mTelephonyListenerManager, mMockWm,
mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
mock(DataUsageController.class), mMockSubDefaults,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 91e9f06..3c5cbb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -61,8 +61,9 @@
// Turn off mobile network support.
when(mMockTm.isDataCapable()).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
- mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+ mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig,
+ Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
mDemoModeController);
@@ -80,8 +81,9 @@
when(mMockTm.getServiceState()).thenReturn(mServiceState);
when(mMockSm.getCompleteActiveSubscriptionInfoList()).thenReturn(Collections.emptyList());
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
- mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+ mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig,
+ Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
mDemoModeController);
@@ -147,8 +149,9 @@
// Turn off mobile network support.
when(mMockTm.isDataCapable()).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
- mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
+ mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig,
+ Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
mDemoModeController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
new file mode 100644
index 0000000..463b336
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TelephonyCallbackTest extends SysuiTestCase {
+
+ private TelephonyCallback mTelephonyCallback = new TelephonyCallback();
+
+ @Test
+ public void testAddListener_ActiveDataSubscriptionIdListener() {
+ assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+ mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {});
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {});
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ }
+
+ @Test
+ public void testAddListener_CallStateListener() {
+ assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+ mTelephonyCallback.addCallStateListener(state -> {});
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.addCallStateListener(state -> {});
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ }
+
+ @Test
+ public void testAddListener_ServiceStateListener() {
+ assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+ mTelephonyCallback.addServiceStateListener(serviceState -> {});
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.addServiceStateListener(serviceState -> {});
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ }
+
+ @Test
+ public void testRemoveListener_ActiveDataSubscriptionIdListener() {
+ ActiveDataSubscriptionIdListener listener = subId -> {};
+ mTelephonyCallback.addActiveDataSubscriptionIdListener(listener);
+ mTelephonyCallback.addActiveDataSubscriptionIdListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+ }
+
+ @Test
+ public void testRemoveListener_CallStateListener() {
+ CallStateListener listener = state -> {};
+ mTelephonyCallback.addCallStateListener(listener);
+ mTelephonyCallback.addCallStateListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.removeCallStateListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.removeCallStateListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+ }
+
+ @Test
+ public void testRemoveListener_ServiceStateListener() {
+ ServiceStateListener listener = serviceState -> {};
+ mTelephonyCallback.addServiceStateListener(listener);
+ mTelephonyCallback.addServiceStateListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.removeServiceStateListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isTrue();
+ mTelephonyCallback.removeServiceStateListener(listener);
+ assertThat(mTelephonyCallback.hasAnyListeners()).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java
new file mode 100644
index 0000000..0d1ac7b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 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.telephony;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+import android.telephony.TelephonyCallback.CallStateListener;
+import android.telephony.TelephonyCallback.ServiceStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TelephonyListenerManagerTest extends SysuiTestCase {
+
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock
+ private TelephonyCallback mTelephonyCallback;
+
+ TelephonyListenerManager mTelephonyListenerManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTelephonyListenerManager = new TelephonyListenerManager(
+ mTelephonyManager, mExecutor, mTelephonyCallback);
+ }
+
+ @Test
+ public void testAddListenerRegisters_ActiveDataSubscriptionIdListener() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ }
+
+ @Test
+ public void testAddListenerRegisters_CallStateListener() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ mTelephonyListenerManager.addCallStateListener(state -> {});
+ mTelephonyListenerManager.addCallStateListener(state -> {});
+ mTelephonyListenerManager.addCallStateListener(state -> {});
+ mTelephonyListenerManager.addCallStateListener(state -> {});
+
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ }
+
+ @Test
+ public void testAddListenerRegisters_ServiceStateListener() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+ mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+ mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+ mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ }
+
+ @Test
+ public void testAddListenerRegisters_mixed() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+ mTelephonyListenerManager.addCallStateListener(state -> {});
+ mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+ mTelephonyListenerManager.addCallStateListener(state -> {});
+ mTelephonyListenerManager.addServiceStateListener(serviceState -> {});
+
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ }
+
+ @Test
+ public void testRemoveListenerUnregisters_ActiveDataSubscriptionIdListener() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ ActiveDataSubscriptionIdListener mListener = subId -> { };
+
+ // Need to add one to actually register
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListener);
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ reset(mTelephonyManager);
+
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener);
+ verify(mTelephonyManager, times(1))
+ .unregisterTelephonyCallback(mTelephonyCallback);
+ }
+
+ @Test
+ public void testRemoveListenerUnregisters_CallStateListener() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ CallStateListener mListener = state -> { };
+
+ // Need to add one to actually register
+ mTelephonyListenerManager.addCallStateListener(mListener);
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ reset(mTelephonyManager);
+
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+ mTelephonyListenerManager.removeCallStateListener(mListener);
+ mTelephonyListenerManager.removeCallStateListener(mListener);
+ mTelephonyListenerManager.removeCallStateListener(mListener);
+ mTelephonyListenerManager.removeCallStateListener(mListener);
+ verify(mTelephonyManager, times(1))
+ .unregisterTelephonyCallback(mTelephonyCallback);
+ }
+
+ @Test
+ public void testRemoveListenerUnregisters_ServiceStateListener() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ ServiceStateListener mListener = serviceState -> { };
+
+ // Need to add one to actually register
+ mTelephonyListenerManager.addServiceStateListener(mListener);
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ reset(mTelephonyManager);
+
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+ mTelephonyListenerManager.removeServiceStateListener(mListener);
+ mTelephonyListenerManager.removeServiceStateListener(mListener);
+ mTelephonyListenerManager.removeServiceStateListener(mListener);
+ mTelephonyListenerManager.removeServiceStateListener(mListener);
+ verify(mTelephonyManager, times(1))
+ .unregisterTelephonyCallback(mTelephonyCallback);
+ }
+
+ @Test
+ public void testRemoveListenerUnregisters_mixed() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ ActiveDataSubscriptionIdListener mListenerA = subId -> { };
+ ServiceStateListener mListenerB = serviceState -> { };
+ CallStateListener mListenerC = state -> { };
+
+ // Need to add one to actually register
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListenerA);
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ reset(mTelephonyManager);
+
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(false);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA);
+ mTelephonyListenerManager.removeServiceStateListener(mListenerB);
+ mTelephonyListenerManager.removeCallStateListener(mListenerC);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA);
+ mTelephonyListenerManager.removeServiceStateListener(mListenerB);
+ mTelephonyListenerManager.removeCallStateListener(mListenerC);
+ verify(mTelephonyManager, times(1))
+ .unregisterTelephonyCallback(mTelephonyCallback);
+ }
+
+ @Test
+ public void testAddListener_noDoubleRegister() {
+ when(mTelephonyCallback.hasAnyListeners()).thenReturn(true);
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+ verify(mTelephonyManager, times(1))
+ .registerTelephonyCallback(mExecutor, mTelephonyCallback);
+
+ reset(mTelephonyManager);
+
+ // A second call to add doesn't register another listener.
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {});
+ verify(mTelephonyManager, never()).registerTelephonyCallback(mExecutor, mTelephonyCallback);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
index 7c7ad53..d3d30f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
@@ -27,6 +27,7 @@
private final FakeSystemClock mClock;
private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>();
private boolean mIgnoreClockUpdates;
+ private boolean mExecuting;
/**
* Initializes a fake executor.
@@ -56,7 +57,9 @@
*/
public boolean runNextReady() {
if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) {
+ mExecuting = true;
mQueuedRunnables.poll().mRunnable.run();
+ mExecuting = false;
return true;
}
@@ -162,6 +165,10 @@
executeDelayed(command, 0);
}
+ public boolean isExecuting() {
+ return mExecuting;
+ }
+
/**
* Run all Executors in a loop until they all report they have no ready work to do.
*
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
index abc283f..87206c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.util.concurrency;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -319,6 +321,18 @@
assertEquals(1, runnable.mRunCount);
}
+ @Test
+ public void testIsExecuting() {
+ FakeSystemClock clock = new FakeSystemClock();
+ FakeExecutor fakeExecutor = new FakeExecutor(clock);
+
+ Runnable runnable = () -> assertThat(fakeExecutor.isExecuting()).isTrue();
+
+ assertThat(fakeExecutor.isExecuting()).isFalse();
+ fakeExecutor.execute(runnable);
+ assertThat(fakeExecutor.isExecuting()).isFalse();
+ }
+
private static class RunnableImpl implements Runnable {
int mRunCount;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index bed76f3..5f7016e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -222,6 +222,7 @@
srcs: [
"java/com/android/server/ConnectivityService.java",
"java/com/android/server/ConnectivityServiceInitializer.java",
+ "java/com/android/server/NetIdManager.java",
"java/com/android/server/TestNetworkService.java",
"java/com/android/server/connectivity/AutodestructReference.java",
"java/com/android/server/connectivity/ConnectivityConstants.java",
@@ -235,7 +236,9 @@
"java/com/android/server/connectivity/NetworkDiagnostics.java",
"java/com/android/server/connectivity/NetworkNotificationManager.java",
"java/com/android/server/connectivity/NetworkRanker.java",
+ "java/com/android/server/connectivity/OsCompat.java",
"java/com/android/server/connectivity/PermissionMonitor.java",
+ "java/com/android/server/connectivity/ProfileNetworkPreferences.java",
"java/com/android/server/connectivity/ProxyTracker.java",
"java/com/android/server/connectivity/QosCallbackAgentConnection.java",
"java/com/android/server/connectivity/QosCallbackTracker.java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index c295778..a9eb2c1 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1141,4 +1141,9 @@
*/
public abstract boolean isPackageFrozen(
@NonNull String packageName, int callingUid, int userId);
+
+ /**
+ * Deletes the OAT artifacts of a package.
+ */
+ public abstract void deleteOatArtifactsOfPackage(String packageName);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1985848..59a1a78 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -145,7 +145,6 @@
import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
-import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTestResultParcelable;
@@ -172,13 +171,14 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.net.networkstack.NetworkStackClientBase;
import android.net.resolv.aidl.DnsHealthEventParcel;
import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Build;
@@ -1121,10 +1121,10 @@
}
/**
- * Get a reference to the NetworkStackClient.
+ * Get a reference to the ModuleNetworkStackClient.
*/
- public NetworkStackClient getNetworkStack() {
- return NetworkStackClient.getInstance();
+ public NetworkStackClientBase getNetworkStack() {
+ return ModuleNetworkStackClient.getInstance(null);
}
/**
@@ -1145,8 +1145,8 @@
/**
* @see NetworkUtils#queryUserAccess(int, int)
*/
- public boolean queryUserAccess(int uid, int netId) {
- return NetworkUtils.queryUserAccess(uid, netId);
+ public boolean queryUserAccess(int uid, Network network, ConnectivityService cs) {
+ return cs.queryUserAccess(uid, network);
}
/**
@@ -1183,7 +1183,8 @@
public ConnectivityService(Context context) {
this(context, getDnsResolver(context), new IpConnectivityLog(),
- NetdService.getInstance(), new Dependencies());
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+ new Dependencies());
}
@VisibleForTesting
@@ -1202,7 +1203,7 @@
mNetworkRanker = new NetworkRanker();
final NetworkRequest defaultInternetRequest = createDefaultRequest();
mDefaultRequest = new NetworkRequestInfo(
- defaultInternetRequest, null,
+ Process.myUid(), defaultInternetRequest, null,
new Binder(), NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
null /* attributionTags */);
mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
@@ -1408,8 +1409,7 @@
if (enable) {
handleRegisterNetworkRequest(new NetworkRequestInfo(
- networkRequest, null,
- new Binder(),
+ Process.myUid(), networkRequest, null, new Binder(),
NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
null /* attributionTags */));
} else {
@@ -1562,7 +1562,7 @@
final int requestId = nri.getActiveRequest() != null
? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
mNetworkInfoBlockingLogs.log(String.format(
- "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId()));
+ "%s %d(%d) on netId %d", action, nri.mAsUid, requestId, net.getNetId()));
}
/**
@@ -2077,6 +2077,8 @@
private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc,
int callerUid, String callerPackageName) {
if (!checkSettingsPermission()) {
+ // There is no need to track the effective UID of the request here. If the caller lacks
+ // the settings permission, the effective UID is the same as the calling ID.
nc.setSingleUid(callerUid);
}
nc.setRequestorUidAndPackageName(callerUid, callerPackageName);
@@ -2904,10 +2906,6 @@
}
pw.println();
- pw.println("NetworkStackClient logs:");
- pw.increaseIndent();
- NetworkStackClient.getInstance().dump(pw);
- pw.decreaseIndent();
pw.println();
pw.println("Permission Monitor:");
@@ -4842,6 +4840,42 @@
nai.networkMonitor().forceReevaluation(uid);
}
+ // TODO: call into netd.
+ private boolean queryUserAccess(int uid, Network network) {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return false;
+
+ // Any UID can use its default network.
+ if (nai == getDefaultNetworkForUid(uid)) return true;
+
+ // Privileged apps can use any network.
+ if (mPermissionMonitor.hasRestrictedNetworksPermission(uid)) {
+ return true;
+ }
+
+ // An unprivileged UID can use a VPN iff the VPN applies to it.
+ if (nai.isVPN()) {
+ return nai.networkCapabilities.appliesToUid(uid);
+ }
+
+ // An unprivileged UID can bypass the VPN that applies to it only if it can protect its
+ // sockets, i.e., if it is the owner.
+ final NetworkAgentInfo vpn = getVpnForUid(uid);
+ if (vpn != null && !vpn.networkAgentConfig.allowBypass
+ && uid != vpn.networkCapabilities.getOwnerUid()) {
+ return false;
+ }
+
+ // The UID's permission must be at least sufficient for the network. Since the restricted
+ // permission was already checked above, that just leaves background networks.
+ if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
+ return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
+ }
+
+ // Unrestricted network. Anyone gets to use it.
+ return true;
+ }
+
/**
* Returns information about the proxy a certain network is using. If given a null network, it
* it will return the proxy for the bound network for the caller app or the default proxy if
@@ -4862,7 +4896,7 @@
return null;
}
return getLinkPropertiesProxyInfo(activeNetwork);
- } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network.getNetId())) {
+ } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network, this)) {
// Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
// caller may not have.
return getLinkPropertiesProxyInfo(network);
@@ -5367,6 +5401,8 @@
boolean mPendingIntentSent;
@Nullable
final Messenger mMessenger;
+
+ // Information about the caller that caused this object to be created.
@Nullable
private final IBinder mBinder;
final int mPid;
@@ -5374,6 +5410,13 @@
final @NetworkCallback.Flag int mCallbackFlags;
@Nullable
final String mCallingAttributionTag;
+
+ // Effective UID of this request. This is different from mUid when a privileged process
+ // files a request on behalf of another UID. This UID is used to determine blocked status,
+ // UID matching, and so on. mUid above is used for permission checks and to enforce the
+ // maximum limit of registered callbacks per UID.
+ final int mAsUid;
+
// In order to preserve the mapping of NetworkRequest-to-callback when apps register
// callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
// maintained for keying off of. This is only a concern when the original nri
@@ -5401,12 +5444,12 @@
return (null == uids) ? new ArraySet<>() : uids;
}
- NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
- @Nullable String callingAttributionTag) {
- this(Collections.singletonList(r), r, pi, callingAttributionTag);
+ NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r,
+ @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
+ this(asUid, Collections.singletonList(r), r, pi, callingAttributionTag);
}
- NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+ NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi,
@Nullable String callingAttributionTag) {
ensureAllNetworkRequestsHaveType(r);
@@ -5417,6 +5460,7 @@
mBinder = null;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
+ mAsUid = asUid;
mNetworkRequestCounter.incrementCountOrThrow(mUid);
/**
* Location sensitive data not included in pending intent. Only included in
@@ -5426,14 +5470,15 @@
mCallingAttributionTag = callingAttributionTag;
}
- NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
+ NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
@Nullable String callingAttributionTag) {
- this(Collections.singletonList(r), r, m, binder, callbackFlags, callingAttributionTag);
+ this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags,
+ callingAttributionTag);
}
- NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+ NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
@@ -5446,6 +5491,7 @@
mBinder = binder;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
+ mAsUid = asUid;
mPendingIntent = null;
mNetworkRequestCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
@@ -5488,18 +5534,19 @@
mBinder = nri.mBinder;
mPid = nri.mPid;
mUid = nri.mUid;
+ mAsUid = nri.mAsUid;
mPendingIntent = nri.mPendingIntent;
mNetworkRequestCounter.incrementCountOrThrow(mUid);
mCallbackFlags = nri.mCallbackFlags;
mCallingAttributionTag = nri.mCallingAttributionTag;
}
- NetworkRequestInfo(@NonNull final NetworkRequest r) {
- this(Collections.singletonList(r));
+ NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) {
+ this(asUid, Collections.singletonList(r));
}
- NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
- this(r, r.get(0), null /* pi */, null /* callingAttributionTag */);
+ NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r) {
+ this(asUid, r, r.get(0), null /* pi */, null /* callingAttributionTag */);
}
// True if this NRI is being satisfied. It also accounts for if the nri has its satisifer
@@ -5535,9 +5582,10 @@
@Override
public String toString() {
- return "uid/pid:" + mUid + "/" + mPid + " active request Id: "
+ final String asUidString = (mAsUid == mUid) ? "" : " asUid: " + mAsUid;
+ return "uid/pid:" + mUid + "/" + mPid + asUidString + " activeRequest: "
+ (mActiveRequest == null ? null : mActiveRequest.requestId)
- + " callback request Id: "
+ + " callbackRequest: "
+ mNetworkRequestForCallback.requestId
+ " " + mRequests
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
@@ -5638,7 +5686,7 @@
}
@Override
- public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
+ public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities,
int reqTypeInt, Messenger messenger, int timeoutMs, IBinder binder,
int legacyType, int callbackFlags, @NonNull String callingPackageName,
@Nullable String callingAttributionTag) {
@@ -5650,6 +5698,12 @@
}
final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities;
final int callingUid = mDeps.getCallingUid();
+ // Privileged callers can track the default network of another UID by passing in a UID.
+ if (asUid != Process.INVALID_UID) {
+ enforceSettingsPermission();
+ } else {
+ asUid = callingUid;
+ }
final NetworkRequest.Type reqType;
try {
reqType = NetworkRequest.Type.values()[reqTypeInt];
@@ -5659,10 +5713,10 @@
switch (reqType) {
case TRACK_DEFAULT:
// If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
- // is unused and will be replaced by ones appropriate for the caller.
- // This allows callers to keep track of the default network for their app.
+ // is unused and will be replaced by ones appropriate for the UID (usually, the
+ // calling app). This allows callers to keep track of the default network.
networkCapabilities = copyDefaultNetworkCapabilitiesForUid(
- defaultNc, callingUid, callingPackageName);
+ defaultNc, asUid, callingUid, callingPackageName);
enforceAccessPermission();
break;
case TRACK_SYSTEM_DEFAULT:
@@ -5714,7 +5768,8 @@
final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), reqType);
final NetworkRequestInfo nri = getNriToRegister(
- networkRequest, messenger, binder, callbackFlags, callingAttributionTag);
+ asUid, networkRequest, messenger, binder, callbackFlags,
+ callingAttributionTag);
if (DBG) log("requestNetwork for " + nri);
// For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
@@ -5741,25 +5796,27 @@
* requests registered to track the default request. If there is currently a per-app default
* tracking the app requestor, then we need to create a version of this nri that mirrors that of
* the tracking per-app default so that callbacks are sent to the app requestor appropriately.
+ * @param asUid the uid on behalf of which to file the request. Different from requestorUid
+ * when a privileged caller is tracking the default network for another uid.
* @param nr the network request for the nri.
* @param msgr the messenger for the nri.
* @param binder the binder for the nri.
* @param callingAttributionTag the calling attribution tag for the nri.
* @return the nri to register.
*/
- private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr,
+ private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr,
@Nullable final Messenger msgr, @Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
@Nullable String callingAttributionTag) {
final List<NetworkRequest> requests;
if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
requests = copyDefaultNetworkRequestsForUid(
- nr.getRequestorUid(), nr.getRequestorPackageName());
+ asUid, nr.getRequestorUid(), nr.getRequestorPackageName());
} else {
requests = Collections.singletonList(nr);
}
return new NetworkRequestInfo(
- requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
+ asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
}
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
@@ -5840,8 +5897,8 @@
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
- NetworkRequestInfo nri =
- new NetworkRequestInfo(networkRequest, operation, callingAttributionTag);
+ NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
+ callingAttributionTag);
if (DBG) log("pendingRequest for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
nri));
@@ -5908,7 +5965,7 @@
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri =
- new NetworkRequestInfo(networkRequest, messenger, binder, callbackFlags,
+ new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags,
callingAttributionTag);
if (VDBG) log("listenForNetwork for " + nri);
@@ -5933,8 +5990,8 @@
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
- NetworkRequestInfo nri =
- new NetworkRequestInfo(networkRequest, operation, callingAttributionTag);
+ NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
+ callingAttributionTag);
if (VDBG) log("pendingListenForNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -6084,33 +6141,37 @@
/**
* Get a copy of the network requests of the default request that is currently tracking the
* given uid.
+ * @param asUid the uid on behalf of which to file the request. Different from requestorUid
+ * when a privileged caller is tracking the default network for another uid.
* @param requestorUid the uid to check the default for.
* @param requestorPackageName the requestor's package name.
* @return a copy of the default's NetworkRequest that is tracking the given uid.
*/
@NonNull
private List<NetworkRequest> copyDefaultNetworkRequestsForUid(
- @NonNull final int requestorUid, @NonNull final String requestorPackageName) {
+ final int asUid, final int requestorUid, @NonNull final String requestorPackageName) {
return copyNetworkRequestsForUid(
- getDefaultRequestTrackingUid(requestorUid).mRequests,
- requestorUid, requestorPackageName);
+ getDefaultRequestTrackingUid(asUid).mRequests,
+ asUid, requestorUid, requestorPackageName);
}
/**
* Copy the given nri's NetworkRequest collection.
* @param requestsToCopy the NetworkRequest collection to be copied.
+ * @param asUid the uid on behalf of which to file the request. Different from requestorUid
+ * when a privileged caller is tracking the default network for another uid.
* @param requestorUid the uid to set on the copied collection.
* @param requestorPackageName the package name to set on the copied collection.
* @return the copied NetworkRequest collection.
*/
@NonNull
private List<NetworkRequest> copyNetworkRequestsForUid(
- @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid,
- @NonNull final String requestorPackageName) {
+ @NonNull final List<NetworkRequest> requestsToCopy, final int asUid,
+ final int requestorUid, @NonNull final String requestorPackageName) {
final List<NetworkRequest> requests = new ArrayList<>();
for (final NetworkRequest nr : requestsToCopy) {
requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid(
- nr.networkCapabilities, requestorUid, requestorPackageName),
+ nr.networkCapabilities, asUid, requestorUid, requestorPackageName),
nr.legacyType, nextNetworkRequestId(), nr.type));
}
return requests;
@@ -6118,17 +6179,17 @@
@NonNull
private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
- @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid,
- @NonNull final String requestorPackageName) {
+ @NonNull final NetworkCapabilities netCapToCopy, final int asUid,
+ final int requestorUid, @NonNull final String requestorPackageName) {
// These capabilities are for a TRACK_DEFAULT callback, so:
// 1. Remove NET_CAPABILITY_VPN, because it's (currently!) the only difference between
// mDefaultRequest and a per-UID default request.
// TODO: stop depending on the fact that these two unrelated things happen to be the same
- // 2. Always set the UIDs to mAsUid. restrictRequestUidsForCallerAndSetRequestorInfo will
+ // 2. Always set the UIDs to asUid. restrictRequestUidsForCallerAndSetRequestorInfo will
// not do this in the case of a privileged application.
final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
- netCap.setSingleUid(requestorUid);
+ netCap.setSingleUid(asUid);
restrictRequestUidsForCallerAndSetRequestorInfo(
netCap, requestorUid, requestorPackageName);
return netCap;
@@ -8034,9 +8095,9 @@
final boolean metered = nai.networkCapabilities.isMetered();
boolean blocked;
- blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges);
+ blocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
blocked |= NetworkPolicyManager.isUidBlocked(
- mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE), metered);
+ mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE), metered);
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
}
@@ -8064,12 +8125,12 @@
NetworkRequestInfo nri = mNetworkRequests.get(nr);
final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked;
- oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges);
+ oldVpnBlocked = isUidBlockedByVpn(nri.mAsUid, oldBlockedUidRanges);
newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges)
- ? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges)
+ ? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges)
: oldVpnBlocked;
- final int blockedReasons = mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE);
+ final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
oldBlocked = oldVpnBlocked || NetworkPolicyManager.isUidBlocked(
blockedReasons, oldMetered);
newBlocked = newVpnBlocked || NetworkPolicyManager.isUidBlocked(
@@ -8104,7 +8165,7 @@
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
- if (nri != null && nri.mUid == uid) {
+ if (nri != null && nri.mAsUid == uid) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg);
}
}
@@ -8869,7 +8930,7 @@
// nri is not bound to the death of callback. Instead, callback.bindToDeath() is set in
// handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the
// callback's binder death.
- final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId);
+ final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId);
final ConnectivityDiagnosticsCallbackInfo cbInfo =
new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
@@ -9353,7 +9414,7 @@
nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
nrs.add(createDefaultRequest());
setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids()));
- final NetworkRequestInfo nri = new NetworkRequestInfo(nrs);
+ final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs);
result.add(nri);
}
return result;
@@ -9524,7 +9585,7 @@
}
// Include this nri if it will be tracked by the new per-app default requests.
final boolean isNriGoingToBeTracked =
- getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest;
+ getDefaultRequestTrackingUid(nri.mAsUid) != mDefaultRequest;
if (isNriGoingToBeTracked) {
defaultCallbackRequests.add(nri);
}
@@ -9546,7 +9607,7 @@
final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>();
for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) {
final NetworkRequestInfo trackingNri =
- getDefaultRequestTrackingUid(callbackRequest.mUid);
+ getDefaultRequestTrackingUid(callbackRequest.mAsUid);
// If this nri is not being tracked, the change it back to an untracked nri.
if (trackingNri == mDefaultRequest) {
@@ -9556,12 +9617,12 @@
continue;
}
- final String requestorPackageName =
- callbackRequest.mRequests.get(0).getRequestorPackageName();
+ final NetworkRequest request = callbackRequest.mRequests.get(0);
callbackRequestsToRegister.add(new NetworkRequestInfo(
callbackRequest,
copyNetworkRequestsForUid(
- trackingNri.mRequests, callbackRequest.mUid, requestorPackageName)));
+ trackingNri.mRequests, callbackRequest.mAsUid,
+ callbackRequest.mUid, request.getRequestorPackageName())));
}
return callbackRequestsToRegister;
}
@@ -9665,7 +9726,7 @@
ranges.add(new UidRange(uid, uid));
}
setNetworkRequestUids(requests, ranges);
- return new NetworkRequestInfo(requests);
+ return new NetworkRequestInfo(Process.myUid(), requests);
}
private NetworkRequest createUnmeteredNetworkRequest() {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7f96aff..5d1ca33 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3377,19 +3377,28 @@
}
@Override
- public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, int reason) {
+ public void notifyAppIoBlocked(String volumeUuid, int uid, int tid,
+ @StorageManager.AppIoBlockedReason int reason) {
enforceExternalStorageService();
mStorageSessionController.notifyAppIoBlocked(volumeUuid, uid, tid, reason);
}
@Override
- public void notifyAppIoResumed(String volumeUuid, int uid, int tid, int reason) {
+ public void notifyAppIoResumed(String volumeUuid, int uid, int tid,
+ @StorageManager.AppIoBlockedReason int reason) {
enforceExternalStorageService();
mStorageSessionController.notifyAppIoResumed(volumeUuid, uid, tid, reason);
}
+ @Override
+ public boolean isAppIoBlocked(String volumeUuid, int uid, int tid,
+ @StorageManager.AppIoBlockedReason int reason) {
+ return isAppIoBlocked(uid);
+ }
+
+
private boolean isAppIoBlocked(int uid) {
return mStorageSessionController.isAppIoBlocked(uid);
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7276c78..411fc67 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -154,6 +154,7 @@
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+ int targetSdk;
boolean matchTelephonyCallbackEvent(int event) {
return (callback != null) && (this.eventList.contains(event));
@@ -919,6 +920,8 @@
}
r.phoneId = phoneId;
r.eventList = events;
+ r.targetSdk = TelephonyPermissions.getTargetSdk(mContext, callingPackage);
+
if (DBG) {
log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
}
@@ -1748,6 +1751,11 @@
TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)
&& idMatchWithoutDefaultPhoneCheck(r.subId, subId)) {
try {
+ if (r.targetSdk <= android.os.Build.VERSION_CODES.R) {
+ telephonyDisplayInfo =
+ getBackwardCompatibleTelephonyDisplayInfo(
+ telephonyDisplayInfo);
+ }
r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
@@ -1759,6 +1767,19 @@
}
}
+ private TelephonyDisplayInfo getBackwardCompatibleTelephonyDisplayInfo(
+ @NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
+ int networkType = telephonyDisplayInfo.getNetworkType();
+ int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType();
+ if (networkType == TelephonyManager.NETWORK_TYPE_NR) {
+ overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+ } else if (networkType == TelephonyManager.NETWORK_TYPE_LTE
+ && overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
+ overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
+ }
+ return new TelephonyDisplayInfo(networkType, overrideNetworkType);
+ }
+
public void notifyCallForwardingChanged(boolean cfi) {
notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
}
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index f566277..09873f4 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -35,7 +35,6 @@
import android.net.RouteInfo;
import android.net.TestNetworkInterface;
import android.net.TestNetworkSpecifier;
-import android.net.util.NetdService;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -86,7 +85,9 @@
mHandler = new Handler(mHandlerThread.getLooper());
mContext = Objects.requireNonNull(context, "missing Context");
- mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance");
+ mNetd = Objects.requireNonNull(
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+ "could not get netd instance");
mCm = mContext.getSystemService(ConnectivityManager.class);
mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
TEST_NETWORK_PROVIDER_NAME);
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 051cd99..cfa110e 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -348,7 +348,8 @@
}
}
- private void enforceCallingUserAndCarrierPrivilege(ParcelUuid subscriptionGroup) {
+ private void enforceCallingUserAndCarrierPrivilege(
+ ParcelUuid subscriptionGroup, String pkgName) {
// Only apps running in the primary (system) user are allowed to configure the VCN. This is
// in line with Telephony's behavior with regards to binding to a Carrier App provided
// CarrierConfigService.
@@ -362,12 +363,15 @@
subscriptionInfos.addAll(subMgr.getSubscriptionsInGroup(subscriptionGroup));
});
- final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);
for (SubscriptionInfo info : subscriptionInfos) {
+ final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(info.getSubscriptionId());
+
// Check subscription is active first; much cheaper/faster check, and an app (currently)
// cannot be carrier privileged for inactive subscriptions.
if (subMgr.isValidSlotIndex(info.getSimSlotIndex())
- && telMgr.hasCarrierPrivileges(info.getSubscriptionId())) {
+ && telMgr.checkCarrierPrivilegesForPackage(pkgName)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
// TODO (b/173717728): Allow configuration for inactive, but manageable
// subscriptions.
// TODO (b/173718661): Check for whole subscription groups at a time.
@@ -535,7 +539,7 @@
mContext.getSystemService(AppOpsManager.class)
.checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
- enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+ enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
@@ -553,11 +557,14 @@
* <p>Implements the IVcnManagementService Binder interface.
*/
@Override
- public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
+ public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) {
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+ requireNonNull(opPkgName, "opPkgName was null");
Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);
- enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+ mContext.getSystemService(AppOpsManager.class)
+ .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
+ enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
@@ -820,8 +827,7 @@
final IBinder cbBinder = callback.asBinder();
final VcnStatusCallbackInfo cbInfo =
- new VcnStatusCallbackInfo(
- subGroup, callback, opPkgName, mDeps.getBinderCallingUid());
+ new VcnStatusCallbackInfo(subGroup, callback, opPkgName, callingUid);
try {
cbBinder.linkToDeath(cbInfo, 0 /* flags */);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 9636641..211999f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -35,11 +35,11 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.sysprop.WatchdogProperties;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.sysprop.WatchdogProperties;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.ZygoteConnectionConstants;
@@ -56,9 +56,9 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
import java.util.HashSet;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/** This class calls its monitor every minute. Killing this process if they don't return **/
public class Watchdog {
@@ -688,7 +688,7 @@
if (mActivity != null) {
mActivity.addErrorToDropBox(
"watchdog", null, "system_server", null, null, null,
- subject, report.toString(), stack, null);
+ subject, report.toString(), stack, null, null, null);
}
FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED,
subject);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 492759f..321e3b1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7706,9 +7706,8 @@
*/
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
- boolean isIncremental = false;
float loadingProgress = 1;
- long millisSinceOldestPendingRead = 0;
+ IncrementalMetrics incrementalMetrics = null;
// Notify package manager service to possibly update package state
if (r != null && r.info != null && r.info.packageName != null) {
final String codePath = r.info.getCodePath();
@@ -7719,8 +7718,7 @@
if (incrementalStatesInfo != null) {
loadingProgress = incrementalStatesInfo.getProgress();
}
- isIncremental = IncrementalManager.isIncrementalPath(codePath);
- if (isIncremental) {
+ if (IncrementalManager.isIncrementalPath(codePath)) {
// Report in the main log about the incremental package
Slog.e(TAG, "App crashed on incremental package " + r.info.packageName
+ " which is " + ((int) (loadingProgress * 100)) + "% loaded.");
@@ -7729,8 +7727,7 @@
if (incrementalService != null) {
final IncrementalManager incrementalManager = new IncrementalManager(
IIncrementalService.Stub.asInterface(incrementalService));
- IncrementalMetrics metrics = incrementalManager.getMetrics(codePath);
- millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead();
+ incrementalMetrics = incrementalManager.getMetrics(codePath);
}
}
}
@@ -7760,7 +7757,9 @@
processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER
: (r != null) ? r.getProcessClassEnum()
: ServerProtoEnums.ERROR_SOURCE_UNKNOWN,
- isIncremental, loadingProgress, millisSinceOldestPendingRead
+ incrementalMetrics != null /* isIncremental */, loadingProgress,
+ incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead()
+ : -1
);
final int relaunchReason = r == null ? RELAUNCH_REASON_NONE
@@ -7773,7 +7772,8 @@
}
addErrorToDropBox(
- eventType, r, processName, null, null, null, null, null, null, crashInfo);
+ eventType, r, processName, null, null, null, null, null, null, crashInfo,
+ new Float(loadingProgress), incrementalMetrics);
mAppErrors.crashApplication(r, crashInfo);
}
@@ -7955,7 +7955,8 @@
FrameworkStatsLog.write(FrameworkStatsLog.WTF_OCCURRED, callingUid, tag, processName,
callingPid, (r != null) ? r.getProcessClassEnum() : 0);
- addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo);
+ addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo,
+ null, null);
return r;
}
@@ -7980,7 +7981,7 @@
for (Pair<String, ApplicationErrorReport.CrashInfo> p = list.poll();
p != null; p = list.poll()) {
addErrorToDropBox("wtf", proc, "system_server", null, null, null, p.first, null, null,
- p.second);
+ p.second, null, null);
}
}
@@ -8069,12 +8070,15 @@
* @param report in long form describing the error, null if absent
* @param dataFile text file to include in the report, null if none
* @param crashInfo giving an application stack trace, null if absent
+ * @param loadingProgress the loading progress of an installed package, range in [0, 1].
+ * @param incrementalMetrics metrics for apps installed on Incremental.
*/
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, String activityShortComponentName,
String parentShortComponentName, ProcessRecord parentProcess,
String subject, final String report, final File dataFile,
- final ApplicationErrorReport.CrashInfo crashInfo) {
+ final ApplicationErrorReport.CrashInfo crashInfo,
+ @Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics) {
// NOTE -- this must never acquire the ActivityManagerService lock,
// otherwise the watchdog may be prevented from resetting the system.
@@ -8135,6 +8139,18 @@
if (crashInfo != null && crashInfo.crashTag != null && !crashInfo.crashTag.isEmpty()) {
sb.append("Crash-Tag: ").append(crashInfo.crashTag).append("\n");
}
+ if (loadingProgress != null) {
+ sb.append("Loading-Progress: ").append(loadingProgress.floatValue()).append("\n");
+ }
+ if (incrementalMetrics != null) {
+ sb.append("Incremental: Yes").append("\n");
+ final long millisSinceOldestPendingRead =
+ incrementalMetrics.getMillisSinceOldestPendingRead();
+ if (millisSinceOldestPendingRead > 0) {
+ sb.append("Millis-Since-Oldest-Pending-Read: ").append(
+ millisSinceOldestPendingRead).append("\n");
+ }
+ }
sb.append("\n");
// Do the rest in a worker thread to avoid blocking the caller on I/O
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 31ea14a..074e8fe 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1627,7 +1627,7 @@
dropBuilder.append(catSw.toString());
FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED);
mService.addErrorToDropBox("lowmem", null, "system_server", null,
- null, null, tag.toString(), dropBuilder.toString(), null, null);
+ null, null, tag.toString(), dropBuilder.toString(), null, null, null, null);
synchronized (mService) {
long now = SystemClock.uptimeMillis();
if (mLastMemUsageReportTime < now) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c5f082a..1839e2a 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -382,10 +382,13 @@
@GuardedBy("mProcLock")
void compactAppSome(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
+ COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ }
}
@GuardedBy("mProcLock")
@@ -396,10 +399,13 @@
&& app.mState.getCurAdj() >= mCompactThrottleMinOomAdj
&& app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
+ COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ }
} else {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "Skipping full compaction for " + app.processName
@@ -412,10 +418,13 @@
@GuardedBy("mProcLock")
void compactAppPersistent(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+ }
}
@GuardedBy("mProcLock")
@@ -427,10 +436,13 @@
@GuardedBy("mProcLock")
void compactAppBfgs(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+ }
}
@GuardedBy("mProcLock")
@@ -954,6 +966,7 @@
pendingAction = opt.getReqCompactAction();
pid = proc.getPid();
name = proc.processName;
+ opt.setHasPendingCompact(false);
// don't compact if the process has returned to perceptible
// and this is only a cached/home/prev compaction
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 4643610..f4ce723 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -47,6 +47,12 @@
private int mLastCompactAction;
/**
+ * This process has been scheduled for a memory compaction.
+ */
+ @GuardedBy("mProcLock")
+ private boolean mPendingCompact;
+
+ /**
* True when the process is frozen.
*/
@GuardedBy("mProcLock")
@@ -101,6 +107,16 @@
}
@GuardedBy("mProcLock")
+ boolean hasPendingCompact() {
+ return mPendingCompact;
+ }
+
+ @GuardedBy("mProcLock")
+ void setHasPendingCompact(boolean pendingCompact) {
+ mPendingCompact = pendingCompact;
+ }
+
+ @GuardedBy("mProcLock")
boolean isFrozen() {
return mFrozen;
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 93f30cc..ab4a2d5 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -300,9 +300,8 @@
}
// Check if package is still being loaded
- boolean isIncremental = false;
float loadingProgress = 1;
- long millisSinceOldestPendingRead = 0;
+ IncrementalMetrics incrementalMetrics = null;
final PackageManagerInternal packageManagerInternal = mService.getPackageManagerInternal();
if (aInfo != null && aInfo.packageName != null) {
IncrementalStatesInfo incrementalStatesInfo =
@@ -312,8 +311,7 @@
loadingProgress = incrementalStatesInfo.getProgress();
}
final String codePath = aInfo.getCodePath();
- isIncremental = IncrementalManager.isIncrementalPath(codePath);
- if (isIncremental) {
+ if (IncrementalManager.isIncrementalPath(codePath)) {
// Report in the main log that the incremental package is still loading
Slog.e(TAG, "App crashed on incremental package " + aInfo.packageName
+ " which is " + ((int) (loadingProgress * 100)) + "% loaded.");
@@ -322,8 +320,7 @@
if (incrementalService != null) {
final IncrementalManager incrementalManager = new IncrementalManager(
IIncrementalService.Stub.asInterface(incrementalService));
- IncrementalMetrics metrics = incrementalManager.getMetrics(codePath);
- millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead();
+ incrementalMetrics = incrementalManager.getMetrics(codePath);
}
}
}
@@ -345,7 +342,7 @@
info.append("Parent: ").append(parentShortComponentName).append("\n");
}
- if (isIncremental) {
+ if (incrementalMetrics != null) {
// Report in the main log about the incremental package
info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
}
@@ -434,12 +431,14 @@
: FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
mApp.getProcessClassEnum(),
(mApp.info != null) ? mApp.info.packageName : "",
- isIncremental, loadingProgress, millisSinceOldestPendingRead);
+ incrementalMetrics != null /* isIncremental */, loadingProgress,
+ incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead()
+ : -1);
final ProcessRecord parentPr = parentProcess != null
? (ProcessRecord) parentProcess.mOwner : null;
mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
parentShortComponentName, parentPr, annotation, report.toString(), tracesFile,
- null);
+ null, new Float(loadingProgress), incrementalMetrics);
if (mApp.getWindowProcessController().appNotResponding(info.toString(),
() -> {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ec2020f..143a1cf 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -334,13 +334,15 @@
volatile boolean mBootCompleted;
/**
- * In this mode, user is always stopped when switched out but locking of user data is
+ * In this mode, user is always stopped when switched out (unless overridden by the
+ * {@code fw.stop_bg_users_on_switch} system property) but locking of user data is
* postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
* Once total number of unlocked users reach mMaxRunningUsers, least recently used user
* will be locked.
*/
@GuardedBy("mLock")
private boolean mDelayUserDataLocking;
+
/**
* Keep track of last active users for mDelayUserDataLocking.
* The latest stopped user is placed in front while the least recently stopped user in back.
@@ -406,10 +408,9 @@
}
}
- private boolean isDelayUserDataLockingEnabled() {
- synchronized (mLock) {
- return mDelayUserDataLocking;
- }
+ private boolean shouldStopBackgroundUsersOnSwitch() {
+ int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1);
+ return property == -1 ? mDelayUserDataLocking : property == 1;
}
void finishUserSwitch(UserState uss) {
@@ -1041,6 +1042,11 @@
void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
final int userId = uss.mHandle.getIdentifier();
+ if (DEBUG_MU) {
+ Slog.i(TAG, "finishUserStopped(%d): allowDelayedLocking=%b", userId,
+ allowDelayedLocking);
+ }
+
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPED, userId);
final boolean stopped;
boolean lockUser = true;
@@ -1153,11 +1159,9 @@
Slog.i(TAG, "finishUserStopped, stopping user:" + userId
+ " lock user:" + userIdToLock);
} else {
- Slog.i(TAG, "finishUserStopped, user:" + userId
- + ",skip locking");
+ Slog.i(TAG, "finishUserStopped, user:" + userId + ", skip locking");
// do not lock
userIdToLock = UserHandle.USER_NULL;
-
}
}
return userIdToLock;
@@ -1192,6 +1196,7 @@
}
private void forceStopUser(@UserIdInt int userId, String reason) {
+ if (DEBUG_MU) Slog.i(TAG, "forceStopUser(%d): %s", userId, reason);
mInjector.activityManagerForceStopPackage(userId, reason);
if (mInjector.getUserManager().isPreCreated(userId)) {
// Don't fire intent for precreated.
@@ -1370,6 +1375,7 @@
private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
+ if (DEBUG_MU) Slog.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
final int callingUid = Binder.getCallingUid();
@@ -1790,20 +1796,28 @@
mUserSwitchObservers.finishBroadcast();
}
- private void stopBackgroundUsersIfEnforced(int oldUserId) {
+ private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) {
// Never stop system user
if (oldUserId == UserHandle.USER_SYSTEM) {
return;
}
- // If running in background is disabled or mDelayUserDataLocking mode, stop the user.
- boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
- oldUserId) || isDelayUserDataLockingEnabled();
- if (!disallowRunInBg) {
- return;
- }
+ boolean hasRestriction =
+ hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
synchronized (mLock) {
- if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
- + " and related users");
+ // If running in background is disabled or mStopBackgroundUsersOnSwitch mode,
+ // stop the user.
+ boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch();
+ if (!disallowRunInBg) {
+ if (DEBUG_MU) {
+ Slog.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related users",
+ oldUserId);
+ }
+ return;
+ }
+ if (DEBUG_MU) {
+ Slog.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users",
+ oldUserId);
+ }
stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
null, null);
}
@@ -1904,7 +1918,7 @@
mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
stopGuestOrEphemeralUserIfBackground(oldUserId);
- stopBackgroundUsersIfEnforced(oldUserId);
+ stopBackgroundUsersOnSwitchIfEnforced(oldUserId);
}
private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
@@ -2594,6 +2608,8 @@
pw.println(" mTargetUserId:" + mTargetUserId);
pw.println(" mLastActiveUsers:" + mLastActiveUsers);
pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking);
+ pw.println(" shouldStopBackgroundUsersOnSwitch:"
+ + shouldStopBackgroundUsersOnSwitch());
pw.println(" mMaxRunningUsers:" + mMaxRunningUsers);
pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
pw.println(" mInitialized:" + mInitialized);
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 0eae661..2ee41ac 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -24,6 +24,7 @@
import android.app.GameManager.GameMode;
import android.app.IGameManagerService;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Environment;
@@ -31,6 +32,8 @@
import android.os.Looper;
import android.os.Message;
import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -40,6 +43,8 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
+
/**
* Service to manage game related features.
*
@@ -58,6 +63,7 @@
static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds
private final Context mContext;
+ private final PackageManager mPackageManager;
private final Object mLock = new Object();
private final Handler mHandler;
@GuardedBy("mLock")
@@ -70,6 +76,13 @@
GameManagerService(Context context, Looper looper) {
mContext = context;
mHandler = new SettingsHandler(looper);
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver result) {
+ new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
}
class SettingsHandler extends Handler {
@@ -171,9 +184,8 @@
}
private boolean isValidPackageName(String packageName) {
- final PackageManager pm = mContext.getPackageManager();
try {
- return pm.getPackageUid(packageName, 0) == Binder.getCallingUid();
+ return mPackageManager.getPackageUid(packageName, 0) == Binder.getCallingUid();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
@@ -208,16 +220,36 @@
@Override
public @GameMode int getGameMode(String packageName, int userId)
throws SecurityException {
- // TODO(b/178860939): Restrict to games only.
-
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "getGameMode",
"com.android.server.app.GameManagerService");
+ // Restrict to games only.
+ try {
+ final ApplicationInfo applicationInfo = mPackageManager
+ .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+ if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
+ Log.e(TAG, "Ignoring attempt to get the Game Mode for '" + packageName
+ + "' which is not categorized as a game: applicationInfo.flags = "
+ + applicationInfo.flags + ", category = " + applicationInfo.category);
+ return GameManager.GAME_MODE_UNSUPPORTED;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return GameManager.GAME_MODE_UNSUPPORTED;
+ }
+
+ // This function handles two types of queries:
+ // 1.) A normal, non-privileged app querying its own Game Mode.
+ // 2.) A privileged system service querying the Game Mode of another package.
+ // The least privileged case is a normal app performing a query, so check that first and
+ // return a value if the package name is valid. Next, check if the caller has the necessary
+ // permission and return a value. Do this check last, since it can throw an exception.
if (isValidPackageName(packageName)) {
return getGameModeFromSettings(packageName, userId);
}
+ // Since the package name doesn't match, check the caller has the necessary permission.
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
return getGameModeFromSettings(packageName, userId);
}
@@ -230,15 +262,28 @@
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public void setGameMode(String packageName, @GameMode int gameMode, int userId)
throws SecurityException {
- // TODO(b/178860939): Restrict to games only.
-
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
- userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, false, true, "setGameMode",
- "com.android.server.app.GameManagerService");
+ // Restrict to games only.
+ try {
+ final ApplicationInfo applicationInfo = mPackageManager
+ .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+ if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
+ Log.e(TAG, "Ignoring attempt to set the Game Mode for '" + packageName
+ + "' which is not categorized as a game: applicationInfo.flags = "
+ + applicationInfo.flags + ", category = " + applicationInfo.category);
+ return;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return;
+ }
synchronized (mLock) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "setGameMode",
+ "com.android.server.app.GameManagerService");
+
if (!mSettings.containsKey(userId)) {
return;
}
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
new file mode 100644
index 0000000..e4c0002
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 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.app;
+
+import android.compat.Compatibility;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.ShellCommand;
+import android.util.ArraySet;
+
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.wm.CompatModePackages;
+
+import java.io.PrintWriter;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * ShellCommands for GameManagerService.
+ *
+ * Use with {@code adb shell cmd game ...}.
+ */
+public class GameManagerShellCommand extends ShellCommand {
+
+ public GameManagerShellCommand() {}
+
+ private static final ArraySet<Long> DOWNSCALE_CHANGE_IDS = new ArraySet<>(new Long[]{
+ CompatModePackages.DOWNSCALED,
+ CompatModePackages.DOWNSCALE_90,
+ CompatModePackages.DOWNSCALE_80,
+ CompatModePackages.DOWNSCALE_70,
+ CompatModePackages.DOWNSCALE_60,
+ CompatModePackages.DOWNSCALE_50});
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "downscale":
+ final String ratio = getNextArgRequired();
+ final String packageName = getNextArgRequired();
+
+ final long changeId;
+ switch (ratio) {
+ case "0.5":
+ changeId = CompatModePackages.DOWNSCALE_50;
+ break;
+ case "0.6":
+ changeId = CompatModePackages.DOWNSCALE_60;
+ break;
+ case "0.7":
+ changeId = CompatModePackages.DOWNSCALE_70;
+ break;
+ case "0.8":
+ changeId = CompatModePackages.DOWNSCALE_80;
+ break;
+ case "0.9":
+ changeId = CompatModePackages.DOWNSCALE_90;
+ break;
+ case "disable":
+ changeId = 0;
+ break;
+ default:
+ changeId = -1;
+ pw.println("Invalid scaling ratio '" + ratio + "'");
+ break;
+ }
+ if (changeId == -1) {
+ break;
+ }
+
+ Set<Long> enabled = new ArraySet<>();
+ Set<Long> disabled;
+ if (changeId == 0) {
+ disabled = DOWNSCALE_CHANGE_IDS;
+ } else {
+ enabled.add(CompatModePackages.DOWNSCALED);
+ enabled.add(changeId);
+ disabled = DOWNSCALE_CHANGE_IDS.stream()
+ .filter(it -> it != CompatModePackages.DOWNSCALED && it != changeId)
+ .collect(Collectors.toSet());
+ }
+
+ final PlatformCompat platformCompat = (PlatformCompat)
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+ final CompatibilityChangeConfig overrides =
+ new CompatibilityChangeConfig(
+ new Compatibility.ChangeConfig(enabled, disabled));
+
+ platformCompat.setOverrides(overrides, packageName);
+ if (changeId == 0) {
+ pw.println("Disable downscaling for " + packageName + ".");
+ } else {
+ pw.println("Enable downscaling ratio for " + packageName + " to " + ratio);
+ }
+
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (Exception e) {
+ pw.println("Error: " + e);
+ }
+ return -1;
+ }
+
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Game manager (game) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" downscale [0.5|0.6|0.7|0.8|0.9|disable] <PACKAGE_NAME>");
+ pw.println(" Force app to run at the specified scaling ratio.");
+ }
+}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index c6824d1..5a99e0e 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -38,6 +38,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Environment;
import android.os.RemoteException;
@@ -69,6 +70,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -91,8 +93,10 @@
private final Object mLock = new Object();
private final Context mContext;
private final IPackageManager mIPackageManager;
+ private final PackageManagerInternal mPackageManagerInternal;
private final IActivityManager mIActivityManager;
private final UserManager mUserManager;
+
@GuardedBy("mLock")
private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>();
private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores =
@@ -101,6 +105,7 @@
private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>();
private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore;
private final Injector mInjector;
+ private final Executor mBackgroundExecutor;
@VisibleForTesting
boolean mIsServiceEnabled;
@@ -123,9 +128,11 @@
super(injector.getContext());
mContext = injector.getContext();
mIPackageManager = injector.getPackageManager();
+ mPackageManagerInternal = injector.getPackageManagerInternal();
mIActivityManager = injector.getActivityManager();
mUserManager = injector.getUserManager();
mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
+ mBackgroundExecutor = injector.getBackgroundExecutor();
mInjector = injector;
final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
@@ -147,11 +154,13 @@
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
- List<GlobalLevelState> states =
- mGlobalLevelHibernationDiskStore.readHibernationStates();
- synchronized (mLock) {
- initializeGlobalHibernationStates(states);
- }
+ mBackgroundExecutor.execute(() -> {
+ List<GlobalLevelState> states =
+ mGlobalLevelHibernationDiskStore.readHibernationStates();
+ synchronized (mLock) {
+ initializeGlobalHibernationStates(states);
+ }
+ });
}
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mIsServiceEnabled = isAppHibernationEnabled();
@@ -170,16 +179,15 @@
* @return true if package is hibernating for the user
*/
boolean isHibernatingForUser(String packageName, int userId) {
- if (!checkHibernationEnabled("isHibernatingForUser")) {
+ String methodName = "isHibernatingForUser";
+ if (!checkHibernationEnabled(methodName)) {
return false;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
- userId = handleIncomingUser(userId, "isHibernating");
- if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
- Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user "
- + userId);
+ userId = handleIncomingUser(userId, methodName);
+ if (!checkUserStatesExist(userId, methodName)) {
return false;
}
synchronized (mLock) {
@@ -210,8 +218,9 @@
synchronized (mLock) {
GlobalLevelState state = mGlobalHibernationStates.get(packageName);
if (state == null) {
- throw new IllegalArgumentException(
- String.format("Package %s is not installed", packageName));
+ // This API can be legitimately called before installation finishes as part of
+ // dex optimization, so we just return false here.
+ return false;
}
return state.hibernated;
}
@@ -225,16 +234,15 @@
* @param isHibernating new hibernation state
*/
void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
- if (!checkHibernationEnabled("setHibernatingForUser")) {
+ String methodName = "setHibernatingForUser";
+ if (!checkHibernationEnabled(methodName)) {
return;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
- userId = handleIncomingUser(userId, "setHibernating");
- if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
- Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user "
- + userId);
+ userId = handleIncomingUser(userId, methodName);
+ if (!checkUserStatesExist(userId, methodName)) {
return;
}
synchronized (mLock) {
@@ -298,16 +306,15 @@
*/
@NonNull List<String> getHibernatingPackagesForUser(int userId) {
ArrayList<String> hibernatingPackages = new ArrayList<>();
- if (!checkHibernationEnabled("getHibernatingPackagesForUser")) {
+ String methodName = "getHibernatingPackagesForUser";
+ if (!checkHibernationEnabled(methodName)) {
return hibernatingPackages;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
- userId = handleIncomingUser(userId, "getHibernatingPackagesForUser");
- if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
- Slog.w(TAG, "Attempt to get hibernating packages for a stopped or nonexistent user "
- + userId);
+ userId = handleIncomingUser(userId, methodName);
+ if (!checkUserStatesExist(userId, methodName)) {
return hibernatingPackages;
}
synchronized (mLock) {
@@ -364,7 +371,7 @@
@GuardedBy("mLock")
private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
- // TODO(175830194): Delete vdex/odex when DexManager API is built out
+ mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName);
state.hibernated = true;
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
@@ -468,10 +475,15 @@
HibernationStateDiskStore<UserLevelState> diskStore =
mInjector.getUserLevelDiskStore(userId);
mUserDiskStores.put(userId, diskStore);
- List<UserLevelState> storedStates = diskStore.readHibernationStates();
- synchronized (mLock) {
- initializeUserHibernationStates(userId, storedStates);
- }
+ mBackgroundExecutor.execute(() -> {
+ List<UserLevelState> storedStates = diskStore.readHibernationStates();
+ synchronized (mLock) {
+ // Ensure user hasn't stopped in the time to execute.
+ if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ initializeUserHibernationStates(userId, storedStates);
+ }
+ }
+ });
}
@Override
@@ -541,6 +553,20 @@
}
}
+ private boolean checkUserStatesExist(int userId, String methodName) {
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ Slog.e(TAG, String.format(
+ "Attempt to call %s on stopped or nonexistent user %d", methodName, userId));
+ return false;
+ }
+ if (!mUserStates.contains(userId)) {
+ Slog.w(TAG, String.format(
+ "Attempt to call %s before states have been read from disk", methodName));
+ return false;
+ }
+ return true;
+ }
+
private boolean checkHibernationEnabled(String methodName) {
if (!mIsServiceEnabled) {
Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName));
@@ -707,10 +733,14 @@
IPackageManager getPackageManager();
+ PackageManagerInternal getPackageManagerInternal();
+
IActivityManager getActivityManager();
UserManager getUserManager();
+ Executor getBackgroundExecutor();
+
HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore();
HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId);
@@ -739,6 +769,11 @@
}
@Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ @Override
public IActivityManager getActivityManager() {
return ActivityManager.getService();
}
@@ -749,6 +784,11 @@
}
@Override
+ public Executor getBackgroundExecutor() {
+ return mScheduledExecutorService;
+ }
+
+ @Override
public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME);
return new HibernationStateDiskStore<>(
diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
index c83659d..24cf433 100644
--- a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
+++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
@@ -109,6 +109,7 @@
* @return the parsed list of hibernation states, null if file does not exist
*/
@Nullable
+ @WorkerThread
List<T> readHibernationStates() {
synchronized (this) {
if (!mHibernationFile.exists()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 81ce2d5..3cfaaf7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -197,7 +197,7 @@
return mListener;
}
- public final int getTargetUserId() {
+ public int getTargetUserId() {
return mTargetUserId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 20c25c3..6c480f1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -55,7 +55,7 @@
private static final String BASE_TAG = "BiometricScheduler";
// Number of recent operations to keep in our logs for dumpsys
- private static final int LOG_NUM_RECENT_OPERATIONS = 50;
+ protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
/**
* Contains all the necessary information for a HAL operation.
@@ -196,10 +196,10 @@
}
}
- @NonNull private final String mBiometricTag;
+ @NonNull protected final String mBiometricTag;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
- @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
+ @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalCallback mInternalCallback;
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
@VisibleForTesting @Nullable Operation mCurrentOperation;
@@ -294,11 +294,11 @@
return mInternalCallback;
}
- private String getTag() {
+ protected String getTag() {
return BASE_TAG + "/" + mBiometricTag;
}
- private void startNextOperationIfIdle() {
+ protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
return;
@@ -310,6 +310,7 @@
mCurrentOperation = mPendingOperations.poll();
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
+ Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send
// ERROR_CANCELED. No need to start this client.
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
new file mode 100644
index 0000000..3d69326
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricsProto;
+
+/**
+ * Abstract class for starting a new user.
+ * @param <T> Interface to request a new user.
+ * @param <U> Newly created user object.
+ */
+public abstract class StartUserClient<T, U> extends HalClientMonitor<T> {
+
+ /**
+ * Invoked when the new user is started.
+ * @param <U> New user object.
+ */
+ public interface UserStartedCallback<U> {
+ void onUserStarted(int newUserId, U newUser);
+ }
+
+ @NonNull @VisibleForTesting
+ protected final UserStartedCallback<U> mUserStartedCallback;
+
+ public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull UserStartedCallback<U> callback) {
+ super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mUserStartedCallback = callback;
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_START_USER;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
new file mode 100644
index 0000000..1f6e1e9
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricsProto;
+
+/**
+ * Abstract class for stopping a user.
+ * @param <T> Interface for stopping the user.
+ */
+public abstract class StopUserClient<T> extends HalClientMonitor<T> {
+
+ public interface UserStoppedCallback {
+ void onUserStopped();
+ }
+
+ @NonNull @VisibleForTesting
+ private final UserStoppedCallback mUserStoppedCallback;
+
+ public void onUserStopped() {
+ mUserStoppedCallback.onUserStopped();
+ getCallback().onClientFinished(this, true /* success */);
+ }
+
+ public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mUserStoppedCallback = callback;
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_STOP_USER;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
new file mode 100644
index 0000000..f015a80
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+
+/**
+ * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
+ */
+public class UserAwareBiometricScheduler extends BiometricScheduler {
+
+ private static final String BASE_TAG = "UaBiometricScheduler";
+
+ /**
+ * Interface to retrieve the owner's notion of the current userId. Note that even though
+ * the scheduler can determine this based on its history of processed clients, we should still
+ * query the owner since it may be cleared due to things like HAL death, etc.
+ */
+ public interface CurrentUserRetriever {
+ int getCurrentUserId();
+ }
+
+ public interface UserSwitchCallback {
+ @NonNull StopUserClient<?> getStopUserClient(int userId);
+ @NonNull StartUserClient<?, ?> getStartUserClient(int newUserId);
+ }
+
+ @NonNull private final CurrentUserRetriever mCurrentUserRetriever;
+ @NonNull private final UserSwitchCallback mUserSwitchCallback;
+ @NonNull @VisibleForTesting final ClientFinishedCallback mClientFinishedCallback;
+
+ @Nullable private StopUserClient<?> mStopUserClient;
+
+ @VisibleForTesting
+ class ClientFinishedCallback implements BaseClientMonitor.Callback {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+ mHandler.post(() -> {
+ Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+
+ startNextOperationIfIdle();
+ });
+ }
+ }
+
+ @VisibleForTesting
+ UserAwareBiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull IBiometricService biometricService,
+ @NonNull CurrentUserRetriever currentUserRetriever,
+ @NonNull UserSwitchCallback userSwitchCallback) {
+ super(tag, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS);
+
+ mCurrentUserRetriever = currentUserRetriever;
+ mUserSwitchCallback = userSwitchCallback;
+ mClientFinishedCallback = new ClientFinishedCallback();
+ }
+
+ public UserAwareBiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull CurrentUserRetriever currentUserRetriever,
+ @NonNull UserSwitchCallback userSwitchCallback) {
+ this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
+ userSwitchCallback);
+ }
+
+ @Override
+ protected String getTag() {
+ return BASE_TAG + "/" + mBiometricTag;
+ }
+
+ @Override
+ protected void startNextOperationIfIdle() {
+ if (mCurrentOperation != null) {
+ Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+ return;
+ }
+ if (mPendingOperations.isEmpty()) {
+ Slog.d(getTag(), "No operations, returning to idle");
+ return;
+ }
+
+ final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
+ final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
+
+ if (nextUserId == currentUserId) {
+ super.startNextOperationIfIdle();
+ } else if (currentUserId == UserHandle.USER_NULL) {
+ final BaseClientMonitor startClient =
+ mUserSwitchCallback.getStartUserClient(nextUserId);
+ Slog.d(getTag(), "[Starting User] " + startClient);
+ startClient.start(mClientFinishedCallback);
+ } else {
+ if (mStopUserClient != null) {
+ Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient);
+ } else {
+ mStopUserClient = mUserSwitchCallback
+ .getStopUserClient(currentUserId);
+ Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+ + ", next: " + nextUserId + ". " + mStopUserClient);
+ mStopUserClient.start(mClientFinishedCallback);
+ }
+ }
+ }
+
+ public void onUserStopped() {
+ if (mStopUserClient == null) {
+ Slog.e(getTag(), "Unexpected onUserStopped");
+ return;
+ }
+
+ Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient);
+ mStopUserClient.onUserStopped();
+ mStopUserClient = null;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 9f5dc69..d10fd4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -502,9 +502,6 @@
return false;
}
- final boolean enrolled = provider.getEnrolledFaces(sensorId, userId).size() > 0;
- Slog.d(TAG, "hasEnrolledFaces, sensor: " + sensorId + ", enrolled: " + enrolled);
-
return provider.getEnrolledFaces(sensorId, userId).size() > 0;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index ca29057..a9be8e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -78,7 +78,6 @@
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
- @NonNull private final HalClientMonitor.LazyDaemon<IFace> mLazyDaemon;
@NonNull private final Handler mHandler;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final UsageStats mUsageStats;
@@ -126,7 +125,6 @@
mContext = context;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
- mLazyDaemon = this::getHalInstance;
mHandler = new Handler(Looper.getMainLooper());
mUsageStats = new UsageStats(context);
mLockoutResetDispatcher = lockoutResetDispatcher;
@@ -163,7 +161,8 @@
}
@Nullable
- private synchronized IFace getHalInstance() {
+ @VisibleForTesting
+ synchronized IFace getHalInstance() {
if (mTestHalEnabled) {
return new TestHal();
}
@@ -214,22 +213,6 @@
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
}
- private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId,
- int userId) throws RemoteException {
- // Note that per IFace createSession contract, this method will block until all
- // existing operations are canceled/finished. However, also note that this is fine, since
- // this method "withoutHandler" means it should only ever be invoked from the worker thread,
- // so callers will never be blocked.
- mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
-
- if (FaceUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) {
- Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId
- + ", user: " + userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
-
-
private void scheduleLoadAuthenticatorIds(int sensorId) {
for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
scheduleLoadAuthenticatorIdsForUser(sensorId, user.id);
@@ -238,37 +221,21 @@
private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId);
- return;
- }
+ final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
-
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
- + ", sensorId: " + sensorId
- + ", userId: " + userId, e);
- }
+ scheduleForSensor(sensorId, client);
});
}
- private void scheduleInvalidationRequest(int sensorId, int userId) {
+ void scheduleInvalidationRequest(int sensorId, int userId) {
mHandler.post(() -> {
final InvalidationRequesterClient<Face> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
FaceUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@@ -303,25 +270,10 @@
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: "
- + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId, sensorId,
- mSensors.get(sensorId).getAuthenticatorIds(), callback);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception", e);
- }
+ final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds(), callback);
+ scheduleForSensor(sensorId, client);
});
}
@@ -344,25 +296,10 @@
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e);
- }
+ final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
+ scheduleForSensor(sensorId, client);
});
}
@@ -370,25 +307,10 @@
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId,
- challenge);
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e);
- }
+ final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId,
+ challenge);
+ scheduleForSensor(sensorId, client);
});
}
@@ -398,41 +320,24 @@
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
@Nullable NativeHandle previewSurface, boolean debugConsent) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final int maxTemplatesPerUser = mSensors.get(
- sensorId).getSensorProperties().maxEnrollmentsPerUser;
- final FaceEnrollClient client = new FaceEnrollClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
- debugConsent);
- scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
+ final int maxTemplatesPerUser = mSensors.get(
+ sensorId).getSensorProperties().maxEnrollmentsPerUser;
+ final FaceEnrollClient client = new FaceEnrollClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
+ ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
+ debugConsent);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
}
- });
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling enroll", e);
- }
+ }
+ });
});
}
@@ -447,31 +352,14 @@
@NonNull String opPackageName, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final FaceAuthenticationClient client = new FaceAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
- mUsageStats, mSensors.get(sensorId).getLockoutCache(),
- allowBackgroundAuthentication);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
- }
+ final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final FaceAuthenticationClient client = new FaceAuthenticationClient(
+ mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ operationId, restricted, opPackageName, cookie,
+ false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ mUsageStats, mSensors.get(sensorId).getLockoutCache(),
+ allowBackgroundAuthentication);
+ scheduleForSensor(sensorId, client);
});
}
@@ -503,56 +391,24 @@
private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceRemovalClient client = new FaceRemovalClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), faceIds, userId,
- opPackageName, FaceUtils.getInstance(sensorId), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
-
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling remove", e);
- }
+ final FaceRemovalClient client = new FaceRemovalClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), faceIds, userId,
+ opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
@Override
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
- return;
- }
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceResetLockoutClient client = new FaceResetLockoutClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
- mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
- }
+ scheduleForSensor(sensorId, client);
});
}
@@ -580,29 +436,14 @@
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
- final FaceInternalCleanupClient client =
- new FaceInternalCleanupClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
- FaceUtils.getInstance(sensorId),
- mSensors.get(sensorId).getAuthenticatorIds());
-
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
- }
+ final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
+ final FaceInternalCleanupClient client =
+ new FaceInternalCleanupClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, enrolledList,
+ FaceUtils.getInstance(sensorId),
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
new file mode 100644
index 0000000..c364dbb
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.ISessionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StartUserClient;
+
+public class FaceStartUserClient extends StartUserClient<IFace, ISession> {
+ private static final String TAG = "FaceStartUserClient";
+
+ @NonNull private final ISessionCallback mSessionCallback;
+
+ public FaceStartUserClient(@NonNull Context context, @NonNull LazyDaemon<IFace> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull ISessionCallback sessionCallback,
+ @NonNull UserStartedCallback<ISession> callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ mSessionCallback = sessionCallback;
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+ getTargetUserId(), mSessionCallback);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+ getCallback().onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
new file mode 100644
index 0000000..8d3853b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StopUserClient;
+
+public class FaceStopUserClient extends StopUserClient<ISession> {
+ private static final String TAG = "FaceStopUserClient";
+
+ public FaceStopUserClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().close(mSequentialId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 3eb4759..768f464 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -33,8 +33,11 @@
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -54,6 +57,9 @@
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.face.FaceUtils;
import java.util.ArrayList;
@@ -70,9 +76,10 @@
@NonNull private final String mTag;
@NonNull private final FaceProvider mProvider;
@NonNull private final Context mContext;
+ @NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
- @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final UserAwareBiometricScheduler mScheduler;
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession;
@@ -112,14 +119,14 @@
@NonNull
private final String mTag;
@NonNull
- private final BiometricScheduler mScheduler;
+ private final UserAwareBiometricScheduler mScheduler;
private final int mSensorId;
private final int mUserId;
@NonNull
private final Callback mCallback;
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
@NonNull Callback callback) {
mContext = context;
mHandler = handler;
@@ -426,9 +433,7 @@
@Override
public void onSessionClosed() {
- mHandler.post(() -> {
- // TODO: implement this.
- });
+ mHandler.post(mScheduler::onUserStopped);
}
}
@@ -437,9 +442,53 @@
mTag = tag;
mProvider = provider;
mContext = context;
+ mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
+ mScheduler = new UserAwareBiometricScheduler(tag, null /* gestureAvailabilityDispatcher */,
+ () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
+ mSensorProperties.sensorId, () -> mCurrentSession = null);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ final HalSessionCallback.Callback callback = () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ };
+
+ final int sensorId = mSensorProperties.sensorId;
+
+ final HalSessionCallback resultController = new HalSessionCallback(mContext,
+ mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+
+ final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+ (userIdStarted, newSession) -> {
+ mCurrentSession = new Session(mTag, newSession, userIdStarted,
+ resultController);
+ if (FaceUtils.getLegacyInstance(sensorId)
+ .isInvalidationInProgress(mContext, userIdStarted)) {
+ Slog.w(mTag,
+ "Scheduling unfinished invalidation request for "
+ + "sensor: "
+ + sensorId
+ + ", user: " + userIdStarted);
+ provider.scheduleInvalidationRequest(sensorId,
+ userIdStarted);
+ }
+ };
+
+ return new FaceStartUserClient(mContext, provider::getHalInstance,
+ mToken, newUserId, mSensorProperties.sensorId,
+ resultController, userStartedCallback);
+ }
+ });
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
@@ -453,11 +502,6 @@
return mSensorProperties;
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean hasSessionForUser(int userId) {
- return mCurrentSession != null && mCurrentSession.mUserId == userId;
- }
-
@Nullable Session getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
return mCurrentSession;
@@ -471,20 +515,6 @@
mProvider, this);
}
- void createNewSession(@NonNull IFace daemon, int sensorId, int userId)
- throws RemoteException {
-
- final HalSessionCallback.Callback callback = () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- };
- final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
- mTag, mScheduler, sensorId, userId, callback);
-
- final ISession newSession = daemon.createSession(sensorId, userId, resultController);
- mCurrentSession = new Session(mTag, newSession, userId, resultController);
- }
-
@NonNull BiometricScheduler getScheduler() {
return mScheduler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 4ca85d0..36327bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -135,7 +135,8 @@
@Override
public void close(int cookie) throws RemoteException {
- cb.onStateChanged(cookie, SessionState.CLOSED);
+ Slog.w(TAG, "close, cookie: " + cookie);
+ cb.onSessionClosed();
}
};
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 1b5def6..972071c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -82,7 +82,6 @@
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
- @NonNull private final HalClientMonitor.LazyDaemon<IFingerprint> mLazyDaemon;
@NonNull private final Handler mHandler;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@@ -131,7 +130,6 @@
mContext = context;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
- mLazyDaemon = this::getHalInstance;
mHandler = new Handler(Looper.getMainLooper());
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -169,7 +167,8 @@
}
@Nullable
- private synchronized IFingerprint getHalInstance() {
+ @VisibleForTesting
+ synchronized IFingerprint getHalInstance() {
if (mTestHalEnabled) {
// Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
// the test HAL for all sensors under that HAL. This can be updated in the future if
@@ -224,21 +223,6 @@
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
}
- private void createNewSessionWithoutHandler(@NonNull IFingerprint daemon, int sensorId,
- int userId) throws RemoteException {
- // Note that per IFingerprint createSession contract, this method will block until all
- // existing operations are canceled/finished. However, also note that this is fine, since
- // this method "withoutHandler" means it should only ever be invoked from the worker thread,
- // so callers will never be blocked.
- mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
-
- if (FingerprintUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) {
- Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId
- + ", user: " + userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
-
@Override
public boolean containsSensor(int sensorId) {
return mSensors.contains(sensorId);
@@ -275,62 +259,32 @@
private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintGetAuthenticatorIdClient client =
- new FingerprintGetAuthenticatorIdClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
- + ", sensorId: " + sensorId
- + ", userId: " + userId, e);
- }
+ final FingerprintGetAuthenticatorIdClient client =
+ new FingerprintGetAuthenticatorIdClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
- private void scheduleInvalidationRequest(int sensorId, int userId) {
+ void scheduleInvalidationRequest(int sensorId, int userId) {
mHandler.post(() -> {
final InvalidationRequesterClient<Fingerprint> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
FingerprintUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@Override
public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
- mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
- }
+ final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
+ scheduleForSensor(sensorId, client);
});
}
@@ -338,26 +292,12 @@
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintGenerateChallengeClient client =
- new FingerprintGenerateChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), opPackageName,
- sensorId);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e);
- }
+ final FingerprintGenerateChallengeClient client =
+ new FingerprintGenerateChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), opPackageName,
+ sensorId);
+ scheduleForSensor(sensorId, client);
});
}
@@ -365,25 +305,11 @@
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintRevokeChallengeClient client =
- new FingerprintRevokeChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- opPackageName, sensorId, challenge);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e);
- }
+ final FingerprintRevokeChallengeClient client =
+ new FingerprintRevokeChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ opPackageName, sensorId, challenge);
+ scheduleForSensor(sensorId, client);
});
}
@@ -392,40 +318,23 @@
int userId, @NonNull IFingerprintServiceReceiver receiver,
@NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
- .maxEnrollmentsPerUser;
- final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- mUdfpsOverlayController, maxTemplatesPerUser, enrollReason);
- scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
+ final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
+ .maxEnrollmentsPerUser;
+ final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ mUdfpsOverlayController, maxTemplatesPerUser, enrollReason);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
}
- });
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling enroll", e);
- }
+ }
+ });
});
}
@@ -439,29 +348,12 @@
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
int statsClient) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during finger detect, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId,
- opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
- statsClient);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling finger detect", e);
- }
+ final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
+ statsClient);
+ scheduleForSensor(sensorId, client);
});
}
@@ -471,31 +363,14 @@
@NonNull String opPackageName, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
- mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
- mUdfpsOverlayController, allowBackgroundAuthentication);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
- }
+ final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+ mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ operationId, restricted, opPackageName, cookie,
+ false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
+ mUdfpsOverlayController, allowBackgroundAuthentication);
+ scheduleForSensor(sensorId, client);
});
}
@@ -535,29 +410,12 @@
int[] fingerprintIds, int userId, @NonNull IFingerprintServiceReceiver receiver,
@NonNull String opPackageName) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling remove", e);
- }
+ final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
+ opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
@@ -565,28 +423,14 @@
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
- final FingerprintInternalCleanupClient client =
- new FingerprintInternalCleanupClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
- FingerprintUtils.getInstance(sensorId),
- mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
- }
+ final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
+ final FingerprintInternalCleanupClient client =
+ new FingerprintInternalCleanupClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, enrolledList,
+ FingerprintUtils.getInstance(sensorId),
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
@@ -611,26 +455,11 @@
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: "
- + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintInvalidationClient client =
- new FingerprintInvalidationClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId, sensorId,
- mSensors.get(sensorId).getAuthenticatorIds(), callback);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception", e);
- }
+ final FingerprintInvalidationClient client =
+ new FingerprintInvalidationClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds(), callback);
+ scheduleForSensor(sensorId, client);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
new file mode 100644
index 0000000..2d40c91
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StartUserClient;
+
+public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> {
+ private static final String TAG = "FingerprintStartUserClient";
+
+ @NonNull private final ISessionCallback mSessionCallback;
+
+ public FingerprintStartUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull ISessionCallback sessionCallback,
+ @NonNull UserStartedCallback<ISession> callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ mSessionCallback = sessionCallback;
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+ getTargetUserId(), mSessionCallback);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+ getCallback().onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
new file mode 100644
index 0000000..ba81357
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StopUserClient;
+
+public class FingerprintStopUserClient extends StopUserClient<ISession> {
+ private static final String TAG = "FingerprintStopUserClient";
+
+ public FingerprintStopUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<ISession> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().close(mSequentialId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index d843bc9..cd12d02 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -24,15 +24,17 @@
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.Error;
-import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.ISessionCallback;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -53,6 +55,9 @@
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -72,9 +77,10 @@
@NonNull private final String mTag;
@NonNull private final FingerprintProvider mProvider;
@NonNull private final Context mContext;
+ @NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final UserAwareBiometricScheduler mScheduler;
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@@ -112,13 +118,13 @@
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final String mTag;
- @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final UserAwareBiometricScheduler mScheduler;
private final int mSensorId;
private final int mUserId;
@NonNull private final Callback mCallback;
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
@NonNull Callback callback) {
mContext = context;
mHandler = handler;
@@ -406,9 +412,7 @@
@Override
public void onSessionClosed() {
- mHandler.post(() -> {
- // TODO: implement this.
- });
+ mHandler.post(mScheduler::onUserStopped);
}
}
@@ -418,9 +422,53 @@
mTag = tag;
mProvider = provider;
mContext = context;
+ mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
+ mScheduler = new UserAwareBiometricScheduler(tag, gestureAvailabilityDispatcher,
+ () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new FingerprintStopUserClient(mContext, mLazySession, mToken,
+ userId, mSensorProperties.sensorId, () -> mCurrentSession = null);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ final HalSessionCallback.Callback callback = () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ };
+
+ final int sensorId = mSensorProperties.sensorId;
+
+ final HalSessionCallback resultController = new HalSessionCallback(mContext,
+ mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+
+ final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+ (userIdStarted, newSession) -> {
+ mCurrentSession = new Session(mTag,
+ newSession, userIdStarted, resultController);
+ if (FingerprintUtils.getInstance(sensorId)
+ .isInvalidationInProgress(mContext, userIdStarted)) {
+ Slog.w(mTag,
+ "Scheduling unfinished invalidation request for "
+ + "sensor: "
+ + sensorId
+ + ", user: " + userIdStarted);
+ provider.scheduleInvalidationRequest(sensorId,
+ userIdStarted);
+ }
+ };
+
+ return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+ mToken, newUserId, mSensorProperties.sensorId,
+ resultController, userStartedCallback);
+ }
+ });
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
@@ -434,11 +482,6 @@
return mSensorProperties;
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean hasSessionForUser(int userId) {
- return mCurrentSession != null && mCurrentSession.mUserId == userId;
- }
-
@Nullable Session getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
return mCurrentSession;
@@ -452,20 +495,6 @@
mProvider, this);
}
- void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
- throws RemoteException {
-
- final HalSessionCallback.Callback callback = () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- };
- final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
- mTag, mScheduler, sensorId, userId, callback);
-
- final ISession newSession = daemon.createSession(sensorId, userId, resultController);
- mCurrentSession = new Session(mTag, newSession, userId, resultController);
- }
-
@NonNull BiometricScheduler getScheduler() {
return mScheduler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 0b7f3ab..31fc068 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -121,7 +121,8 @@
@Override
public void close(int cookie) throws RemoteException {
- cb.onStateChanged(cookie, SessionState.CLOSED);
+ Slog.w(TAG, "close, cookie: " + cookie);
+ cb.onSessionClosed();
}
@Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 781bad7..41bc0b9 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -121,6 +121,17 @@
return false;
}
+ private void closePipe() {
+ try {
+ final RandomAccessFile pipe = mPipe;
+ mPipe = null;
+ if (pipe != null) {
+ pipe.close();
+ }
+ } catch (IOException ignore) {
+ }
+ }
+
public HostClipboardMonitor(HostClipboardCallback cb) {
mHostClipboardCallback = cb;
}
@@ -142,10 +153,7 @@
mHostClipboardCallback.onHostClipboardUpdated(
new String(receivedData));
} catch (IOException e) {
- try {
- mPipe.close();
- } catch (IOException ee) {}
- mPipe = null;
+ closePipe();
} catch (InterruptedException e) {}
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index f3d2012..6ea84ce 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -18,12 +18,14 @@
import static android.util.TimeUtils.NANOS_PER_MS;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetdEventCallback;
import android.net.MacAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.net.metrics.ConnectStats;
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
@@ -98,6 +100,7 @@
private final TokenBucket mConnectTb =
new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
+ final TransportForNetIdNetworkCallback mCallback = new TransportForNetIdNetworkCallback();
/**
* There are only 3 possible callbacks.
@@ -158,6 +161,9 @@
public NetdEventListenerService(ConnectivityManager cm) {
// We are started when boot is complete, so ConnectivityService should already be running.
mCm = cm;
+ // Clear all capabilities to listen all networks.
+ mCm.registerNetworkCallback(new NetworkRequest.Builder().clearCapabilities().build(),
+ mCallback);
}
private static long projectSnapshotTime(long timeMs) {
@@ -389,18 +395,13 @@
}
private long getTransports(int netId) {
- // TODO: directly query ConnectivityService instead of going through Binder interface.
- NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
+ final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
if (nc == null) {
return 0;
}
return BitUtils.packBits(nc.getTransportTypes());
}
- private static void maybeLog(String s, Object... args) {
- if (DBG) Log.d(TAG, String.format(s, args));
- }
-
/** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
static class NetworkMetricsSnapshot {
@@ -428,4 +429,29 @@
return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
}
}
+
+ private class TransportForNetIdNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final SparseArray<NetworkCapabilities> mCapabilities = new SparseArray<>();
+
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ synchronized (mCapabilities) {
+ mCapabilities.put(network.getNetId(), nc);
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ synchronized (mCapabilities) {
+ mCapabilities.remove(network.getNetId());
+ }
+ }
+
+ @Nullable
+ public NetworkCapabilities getNetworkCapabilities(int netId) {
+ synchronized (mCapabilities) {
+ return mCapabilities.get(netId);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/OsCompat.java b/services/core/java/com/android/server/connectivity/OsCompat.java
new file mode 100644
index 0000000..57e3dcd
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/OsCompat.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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.connectivity;
+
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
+
+/**
+ * Compatibility utility for android.system.Os core platform APIs.
+ *
+ * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
+ * (only core_current). Most stable core platform APIs are included manually in the connectivity
+ * build rules, but because Os is also part of the base java SDK that is earlier on the
+ * classpath, the extra core platform APIs are not seen.
+ *
+ * TODO (b/157639992, b/183097033): remove as soon as core_current is part of system_server_current
+ * @hide
+ */
+public class OsCompat {
+ // This value should be correct on all architectures supported by Android, but hardcoding ioctl
+ // numbers should be avoided.
+ /**
+ * @see android.system.OsConstants#TIOCOUTQ
+ */
+ public static final int TIOCOUTQ = 0x5411;
+
+ /**
+ * @see android.system.Os#getsockoptInt(FileDescriptor, int, int)
+ */
+ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws
+ ErrnoException {
+ try {
+ return (int) Os.class.getMethod(
+ "getsockoptInt", FileDescriptor.class, int.class, int.class)
+ .invoke(null, fd, level, option);
+ } catch (ReflectiveOperationException e) {
+ if (e.getCause() instanceof ErrnoException) {
+ throw (ErrnoException) e.getCause();
+ }
+ throw new IllegalStateException("Error calling getsockoptInt", e);
+ }
+ }
+
+ /**
+ * @see android.system.Os#ioctlInt(FileDescriptor, int)
+ */
+ public static int ioctlInt(FileDescriptor fd, int cmd) throws
+ ErrnoException {
+ try {
+ return (int) Os.class.getMethod(
+ "ioctlInt", FileDescriptor.class, int.class).invoke(null, fd, cmd);
+ } catch (ReflectiveOperationException e) {
+ if (e.getCause() instanceof ErrnoException) {
+ throw (ErrnoException) e.getCause();
+ }
+ throw new IllegalStateException("Error calling ioctlInt", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 488677a..3711679 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -271,6 +271,13 @@
return mApps.containsKey(uid);
}
+ /**
+ * Returns whether the given uid has permission to use restricted networks.
+ */
+ public synchronized boolean hasRestrictedNetworksPermission(int uid) {
+ return Boolean.TRUE.equals(mApps.get(uid));
+ }
+
private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
List<Integer> network = new ArrayList<>();
List<Integer> system = new ArrayList<>();
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index c480594..73f3475 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -27,7 +27,8 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IP_TOS;
import static android.system.OsConstants.IP_TTL;
-import static android.system.OsConstants.TIOCOUTQ;
+
+import static com.android.server.connectivity.OsCompat.TIOCOUTQ;
import android.annotation.NonNull;
import android.net.InvalidPacketException;
@@ -175,10 +176,10 @@
}
// Query write sequence number from SEND_QUEUE.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
- tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ tcpDetails.seq = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
// Query read sequence number from RECV_QUEUE.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
- tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ tcpDetails.ack = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
// Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
// Finally, check if socket is still idle. TODO : this check needs to move to
@@ -198,9 +199,9 @@
tcpDetails.rcvWndScale = trw.rcvWndScale;
if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
// Query TOS.
- tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+ tcpDetails.tos = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
// Query TTL.
- tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+ tcpDetails.ttl = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
}
} catch (ErrnoException e) {
Log.e(TAG, "Exception reading TCP state from socket", e);
@@ -305,7 +306,7 @@
private static boolean isReceiveQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- final int result = Os.ioctlInt(fd, SIOCINQ);
+ final int result = OsCompat.ioctlInt(fd, SIOCINQ);
if (result != 0) {
Log.e(TAG, "Read queue has data");
return false;
@@ -315,7 +316,7 @@
private static boolean isSendQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- final int result = Os.ioctlInt(fd, SIOCOUTQ);
+ final int result = OsCompat.ioctlInt(fd, SIOCOUTQ);
if (result != 0) {
Log.e(TAG, "Write queue has data");
return false;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 64173bb..a070f27 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1133,6 +1133,8 @@
* @return a Network if there is a running VPN network or null if there is no running VPN
* network or network is null.
*/
+ @VisibleForTesting
+ @Nullable
public synchronized Network getNetwork() {
final NetworkAgent agent = mNetworkAgent;
if (null == agent) return null;
@@ -1248,8 +1250,9 @@
mLegacyState = LegacyVpnInfo.STATE_CONNECTING;
updateState(DetailedState.CONNECTING, "agentConnect");
- NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder().build();
- networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
+ final NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder()
+ .setBypassableVpn(mConfig.allowBypass && !mLockdown)
+ .build();
capsBuilder.setOwnerUid(mOwnerUID);
capsBuilder.setAdministratorUids(new int[] {mOwnerUID});
@@ -1858,22 +1861,13 @@
/**
* Updates underlying network set.
*/
- public synchronized boolean setUnderlyingNetworks(Network[] networks) {
+ public synchronized boolean setUnderlyingNetworks(@Nullable Network[] networks) {
if (!isCallerEstablishedOwnerLocked()) {
return false;
}
- if (networks == null) {
- mConfig.underlyingNetworks = null;
- } else {
- mConfig.underlyingNetworks = new Network[networks.length];
- for (int i = 0; i < networks.length; ++i) {
- if (networks[i] == null) {
- mConfig.underlyingNetworks[i] = null;
- } else {
- mConfig.underlyingNetworks[i] = new Network(networks[i].getNetId());
- }
- }
- }
+ // Make defensive copy since the content of array might be altered by the caller.
+ mConfig.underlyingNetworks =
+ (networks != null) ? Arrays.copyOf(networks, networks.length) : null;
mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
? Arrays.asList(mConfig.underlyingNetworks) : null);
return true;
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index ac7e01e..1786a51 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -3978,6 +3978,9 @@
* @return true if the provided key is used by the SyncManager in scheduling the sync.
*/
private static boolean isSyncSetting(String key) {
+ if (key == null) {
+ return false;
+ }
if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) {
return true;
}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index d0e7e45..58308d8 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -250,7 +250,19 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef({
- ABORT_NO_ERROR,
+ NOT_HANDLED,
+ HANDLED,
+ ABORT_UNRECOGNIZED_OPCODE,
+ ABORT_NOT_IN_CORRECT_MODE,
+ ABORT_CANNOT_PROVIDE_SOURCE,
+ ABORT_INVALID_OPERAND,
+ ABORT_REFUSED,
+ ABORT_UNABLE_TO_DETERMINE,
+ })
+ public @interface HandleMessageResult {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
ABORT_UNRECOGNIZED_OPCODE,
ABORT_NOT_IN_CORRECT_MODE,
ABORT_CANNOT_PROVIDE_SOURCE,
@@ -260,8 +272,11 @@
})
public @interface AbortReason {}
- // Internal abort error code. It's the same as success.
- static final int ABORT_NO_ERROR = -1;
+ // Indicates that a message was not handled, but could be handled by another local device.
+ // If no local devices handle the message, we send <Feature Abort>[Unrecognized Opcode].
+ static final int NOT_HANDLED = -2;
+ // Indicates that a message has been handled successfully; no feature abort needed.
+ static final int HANDLED = -1;
// Constants related to operands of HDMI CEC commands.
// Refer to CEC Table 29 in HDMI Spec v1.4b.
// [Abort Reason]
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 1643ec1..ad2ef2a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -565,19 +565,24 @@
}
@ServiceThreadOnly
- private void onReceiveCommand(HdmiCecMessage message) {
+ @VisibleForTesting
+ void onReceiveCommand(HdmiCecMessage message) {
assertRunOnServiceThread();
- if ((isAcceptableAddress(message.getDestination())
- || !mService.isAddressAllocated())
- && mService.handleCecCommand(message)) {
+ if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) {
return;
}
- // Not handled message, so we will reply it with <Feature Abort>.
- maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+ @Constants.HandleMessageResult int messageState = mService.handleCecCommand(message);
+ if (messageState == Constants.NOT_HANDLED) {
+ // Message was not handled
+ maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+ } else if (messageState != Constants.HANDLED) {
+ // Message handler wants to send a feature abort
+ maySendFeatureAbortCommand(message, messageState);
+ }
}
@ServiceThreadOnly
- void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) {
+ void maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason) {
assertRunOnServiceThread();
// Swap the source and the destination.
int src = message.getDestination();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index bdc4e66..505e743 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -248,11 +248,13 @@
* @return true if consumed a message; otherwise, return false.
*/
@ServiceThreadOnly
- boolean dispatchMessage(HdmiCecMessage message) {
+ @VisibleForTesting
+ @Constants.HandleMessageResult
+ protected int dispatchMessage(HdmiCecMessage message) {
assertRunOnServiceThread();
int dest = message.getDestination();
if (dest != mAddress && dest != Constants.ADDR_BROADCAST) {
- return false;
+ return Constants.NOT_HANDLED;
}
// Cache incoming message if it is included in the list of cacheable opcodes.
mCecMessageCache.cacheMessage(message);
@@ -260,10 +262,11 @@
}
@ServiceThreadOnly
- protected final boolean onMessage(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected final int onMessage(HdmiCecMessage message) {
assertRunOnServiceThread();
if (dispatchMessageToAction(message)) {
- return true;
+ return Constants.HANDLED;
}
switch (message.getOpcode()) {
case Constants.MESSAGE_ACTIVE_SOURCE:
@@ -357,7 +360,7 @@
case Constants.MESSAGE_GIVE_FEATURES:
return handleGiveFeatures(message);
default:
- return false;
+ return Constants.NOT_HANDLED;
}
}
@@ -375,7 +378,8 @@
}
@ServiceThreadOnly
- protected boolean handleGivePhysicalAddress(@Nullable SendMessageCallback callback) {
+ @Constants.HandleMessageResult
+ protected int handleGivePhysicalAddress(@Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
int physicalAddress = mService.getPhysicalAddress();
@@ -383,76 +387,83 @@
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
mAddress, physicalAddress, mDeviceType);
mService.sendCecCommand(cecMessage, callback);
- return true;
+ return Constants.HANDLED;
}
@ServiceThreadOnly
- protected boolean handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) {
+ @Constants.HandleMessageResult
+ protected int handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
int vendorId = mService.getVendorId();
HdmiCecMessage cecMessage =
HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, vendorId);
mService.sendCecCommand(cecMessage, callback);
- return true;
+ return Constants.HANDLED;
}
@ServiceThreadOnly
- protected boolean handleGetCecVersion(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGetCecVersion(HdmiCecMessage message) {
assertRunOnServiceThread();
int version = mService.getCecVersion();
HdmiCecMessage cecMessage =
HdmiCecMessageBuilder.buildCecVersion(
message.getDestination(), message.getSource(), version);
mService.sendCecCommand(cecMessage);
- return true;
+ return Constants.HANDLED;
}
@ServiceThreadOnly
- private boolean handleCecVersion() {
+ @Constants.HandleMessageResult
+ protected int handleCecVersion() {
assertRunOnServiceThread();
// Return true to avoid <Feature Abort> responses. Cec Version is tracked in HdmiCecNetwork.
- return true;
+ return Constants.HANDLED;
}
@ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleActiveSource(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
@ServiceThreadOnly
- protected boolean handleInactiveSource(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleInactiveSource(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
@ServiceThreadOnly
- protected boolean handleRequestActiveSource(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRequestActiveSource(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
@ServiceThreadOnly
- protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
- // 'return false' will cause to reply with <Feature Abort>.
- return false;
+ return Constants.NOT_HANDLED;
}
@ServiceThreadOnly
- protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
Slog.w(TAG, "Only Playback device can handle <Set Menu Language>:" + message.toString());
- // 'return false' will cause to reply with <Feature Abort>.
- return false;
+ return Constants.NOT_HANDLED;
}
@ServiceThreadOnly
- protected boolean handleGiveOsdName(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGiveOsdName(HdmiCecMessage message) {
assertRunOnServiceThread();
// Note that since this method is called after logical address allocation is done,
// mDeviceInfo should not be null.
buildAndSendSetOsdName(message.getSource());
- return true;
+ return Constants.HANDLED;
}
protected void buildAndSendSetOsdName(int dest) {
@@ -475,18 +486,21 @@
// Audio System device with no Playback device type
// needs to refactor this function if it's also a switch
- protected boolean handleRoutingChange(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRoutingChange(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
// Audio System device with no Playback device type
// needs to refactor this function if it's also a switch
- protected boolean handleRoutingInformation(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRoutingInformation(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
@CallSuper
- protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleReportPhysicalAddress(HdmiCecMessage message) {
// <Report Physical Address> is also handled in HdmiCecNetwork to update the local network
// state
@@ -495,7 +509,7 @@
// Ignore if [Device Discovery Action] is going on.
if (hasAction(DeviceDiscoveryAction.class)) {
Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
- return true;
+ return Constants.HANDLED;
}
HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address);
@@ -506,63 +520,77 @@
HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
}
- return true;
+ return Constants.HANDLED;
}
- protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleSetSystemAudioMode(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleSystemAudioModeRequest(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleTerminateArc(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleTerminateArc(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleInitiateArc(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleInitiateArc(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRequestArcInitiate(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleRequestArcTermination(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRequestArcTermination(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleReportArcInitiate(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleReportArcInitiate(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleReportArcTermination(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleReportArcTermination(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleReportAudioStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleReportAudioStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleGiveAudioStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleReportShortAudioDescriptor(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleReportShortAudioDescriptor(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
@Constants.RcProfile
@@ -572,13 +600,14 @@
protected abstract List<Integer> getDeviceFeatures();
- protected boolean handleGiveFeatures(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGiveFeatures(HdmiCecMessage message) {
if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
- return false;
+ return Constants.ABORT_UNRECOGNIZED_OPCODE;
}
reportFeatures();
- return true;
+ return Constants.HANDLED;
}
protected void reportFeatures() {
@@ -598,32 +627,34 @@
}
@ServiceThreadOnly
- protected boolean handleStandby(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleStandby(HdmiCecMessage message) {
assertRunOnServiceThread();
// Seq #12
if (mService.isControlEnabled()
&& !mService.isProhibitMode()
&& mService.isPowerOnOrTransient()) {
mService.standby();
- return true;
+ return Constants.HANDLED;
}
- return false;
+ return Constants.ABORT_NOT_IN_CORRECT_MODE;
}
@ServiceThreadOnly
- protected boolean handleUserControlPressed(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleUserControlPressed(HdmiCecMessage message) {
assertRunOnServiceThread();
mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
mService.standby();
- return true;
+ return Constants.HANDLED;
} else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
mService.wakeUp();
- return true;
+ return Constants.HANDLED;
} else if (mService.getHdmiCecVolumeControl()
== HdmiControlManager.VOLUME_CONTROL_DISABLED && isVolumeOrMuteCommand(
message)) {
- return false;
+ return Constants.ABORT_REFUSED;
}
if (isPowerOffOrToggleCommand(message) || isPowerOnOrToggleCommand(message)) {
@@ -631,7 +662,7 @@
// keycode to Android keycode.
// Do not <Feature Abort> as the local device should already be in the correct power
// state.
- return true;
+ return Constants.HANDLED;
}
final long downTime = SystemClock.uptimeMillis();
@@ -653,15 +684,15 @@
mHandler.sendMessageDelayed(
Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT),
FOLLOWER_SAFETY_TIMEOUT);
- return true;
+ return Constants.HANDLED;
}
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
- return true;
+ return Constants.ABORT_INVALID_OPERAND;
}
@ServiceThreadOnly
- protected boolean handleUserControlReleased() {
+ @Constants.HandleMessageResult
+ protected int handleUserControlReleased() {
assertRunOnServiceThread();
mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
mLastKeyRepeatCount = 0;
@@ -670,7 +701,7 @@
injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
}
- return true;
+ return Constants.HANDLED;
}
static void injectKeyEvent(long time, int action, int keycode, int repeat) {
@@ -717,38 +748,45 @@
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
}
- protected boolean handleTextViewOn(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleTextViewOn(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleImageViewOn(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleImageViewOn(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleSetStreamPath(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleSetStreamPath(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGiveDevicePowerStatus(HdmiCecMessage message) {
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportPowerStatus(
mAddress, message.getSource(), mService.getPowerStatus()));
- return true;
+ return Constants.HANDLED;
}
- protected boolean handleMenuRequest(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleMenuRequest(HdmiCecMessage message) {
// Always report menu active to receive Remote Control.
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportMenuStatus(
mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED));
- return true;
+ return Constants.HANDLED;
}
- protected boolean handleMenuStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleMenuStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleVendorCommand(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleVendorCommand(HdmiCecMessage message) {
if (!mService.invokeVendorCommandListenersOnReceived(
mDeviceType,
message.getSource(),
@@ -757,57 +795,64 @@
false)) {
// Vendor command listener may not have been registered yet. Respond with
// <Feature Abort> [Refused] so that the sender can try again later.
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return Constants.ABORT_REFUSED;
}
- return true;
+ return Constants.HANDLED;
}
- protected boolean handleVendorCommandWithId(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleVendorCommandWithId(HdmiCecMessage message) {
byte[] params = message.getParams();
int vendorId = HdmiUtils.threeBytesToInt(params);
if (vendorId == mService.getVendorId()) {
if (!mService.invokeVendorCommandListenersOnReceived(
mDeviceType, message.getSource(), message.getDestination(), params, true)) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return Constants.ABORT_REFUSED;
}
} else if (message.getDestination() != Constants.ADDR_BROADCAST
&& message.getSource() != Constants.ADDR_UNREGISTERED) {
Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+ return Constants.ABORT_UNRECOGNIZED_OPCODE;
} else {
Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
}
- return true;
+ return Constants.HANDLED;
}
protected void sendStandby(int deviceId) {
// Do nothing.
}
- protected boolean handleSetOsdName(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSetOsdName(HdmiCecMessage message) {
// <Set OSD name> is also handled in HdmiCecNetwork to update the local network state
- return true;
+ return Constants.HANDLED;
}
- protected boolean handleRecordTvScreen(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRecordTvScreen(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleTimerClearedStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleReportPowerStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleReportPowerStatus(HdmiCecMessage message) {
// <Report Power Status> is also handled in HdmiCecNetwork to update the local network state
- return true;
+ return Constants.HANDLED;
}
- protected boolean handleTimerStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleTimerStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
- protected boolean handleRecordStatus(HdmiCecMessage message) {
- return false;
+ @Constants.HandleMessageResult
+ protected int handleRecordStatus(HdmiCecMessage message) {
+ return Constants.NOT_HANDLED;
}
@ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index bf5bf8b..790c067 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -316,7 +316,8 @@
@Override
@ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
int logicalAddress = message.getSource();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
@@ -339,52 +340,56 @@
mDelayedMessageBuffer.removeActiveSource();
return super.handleActiveSource(message);
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleInitiateArc(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleInitiateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement initiate arc handler
HdmiLogger.debug(TAG + "Stub handleInitiateArc");
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleReportArcInitiate(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleReportArcInitiate(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement report arc initiate handler
HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleReportArcTermination(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleReportArcTermination(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement report arc terminate handler
HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGiveAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl()
== HdmiControlManager.VOLUME_CONTROL_ENABLED) {
reportAudioStatus(message.getSource());
- } else {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return Constants.HANDLED;
}
- return true;
+ return Constants.ABORT_REFUSED;
}
@Override
@ServiceThreadOnly
- protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
// If the audio system is initiating the system audio mode on and TV asks the sam status at
// the same time, respond with true. Since we know TV supports sam in this situation.
@@ -399,52 +404,53 @@
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportSystemAudioMode(
mAddress, message.getSource(), isSystemAudioModeOnOrTurningOn));
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRequestArcInitiate(HdmiCecMessage message) {
assertRunOnServiceThread();
removeAction(ArcInitiationActionFromAvr.class);
if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+ return Constants.ABORT_UNRECOGNIZED_OPCODE;
} else if (!isDirectConnectToTv()) {
HdmiLogger.debug("AVR device is not directly connected with TV");
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
+ return Constants.ABORT_NOT_IN_CORRECT_MODE;
} else {
addAndStartAction(new ArcInitiationActionFromAvr(this));
+ return Constants.HANDLED;
}
- return true;
}
@Override
@ServiceThreadOnly
- protected boolean handleRequestArcTermination(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRequestArcTermination(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
+ return Constants.ABORT_UNRECOGNIZED_OPCODE;
} else if (!isArcEnabled()) {
HdmiLogger.debug("ARC is not established between TV and AVR device");
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
+ return Constants.ABORT_NOT_IN_CORRECT_MODE;
} else {
removeAction(ArcTerminationActionFromAvr.class);
addAndStartAction(new ArcTerminationActionFromAvr(this));
+ return Constants.HANDLED;
}
- return true;
}
@ServiceThreadOnly
- protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) {
assertRunOnServiceThread();
HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
if (!isSystemAudioControlFeatureEnabled()) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
- return true;
+ return Constants.ABORT_REFUSED;
}
if (!isSystemAudioActivated()) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
- return true;
+ return Constants.ABORT_NOT_IN_CORRECT_MODE;
}
List<DeviceConfig> config = null;
@@ -468,21 +474,20 @@
} else {
AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
if (deviceInfo == null) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE);
- return true;
+ return Constants.ABORT_UNABLE_TO_DETERMINE;
}
sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes);
}
if (sadBytes.length == 0) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
+ return Constants.ABORT_INVALID_OPERAND;
} else {
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
mAddress, message.getSource(), sadBytes));
+ return Constants.HANDLED;
}
- return true;
}
private byte[] getSupportedShortAudioDescriptors(
@@ -624,7 +629,8 @@
@Override
@ServiceThreadOnly
- protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSystemAudioModeRequest(HdmiCecMessage message) {
assertRunOnServiceThread();
boolean systemAudioStatusOn = message.getParams().length != 0;
// Check if the request comes from a non-TV device.
@@ -632,8 +638,7 @@
// if non-TV device tries to turn on the feature
if (message.getSource() != Constants.ADDR_TV) {
if (systemAudioStatusOn) {
- handleSystemAudioModeOnFromNonTvDevice(message);
- return true;
+ return handleSystemAudioModeOnFromNonTvDevice(message);
}
} else {
// If TV request the feature on
@@ -644,8 +649,7 @@
// If TV or Audio System does not support the feature,
// will send abort command.
if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
- return true;
+ return Constants.ABORT_REFUSED;
}
mService.sendCecCommand(
@@ -660,7 +664,7 @@
if (HdmiUtils.getLocalPortFromPhysicalAddress(
sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress())
!= HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
- return true;
+ return Constants.HANDLED;
}
HdmiDeviceInfo safeDeviceInfoByPath =
mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress);
@@ -668,29 +672,31 @@
switchInputOnReceivingNewActivePath(sourcePhysicalAddress);
}
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSetSystemAudioMode(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!checkSupportAndSetSystemAudioMode(
HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return Constants.ABORT_REFUSED;
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!checkSupportAndSetSystemAudioMode(
HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return Constants.ABORT_REFUSED;
}
- return true;
+ return Constants.HANDLED;
}
@ServiceThreadOnly
@@ -948,13 +954,13 @@
/**
* Handler of System Audio Mode Request on from non TV device
*/
- void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ int handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
if (!isSystemAudioControlFeatureEnabled()) {
HdmiLogger.debug(
"Cannot turn on" + "system audio mode "
+ "because the System Audio Control feature is disabled.");
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
- return;
+ return Constants.ABORT_REFUSED;
}
// Wake up device
mService.wakeUp();
@@ -967,7 +973,7 @@
mService.sendCecCommand(
HdmiCecMessageBuilder.buildSetSystemAudioMode(
mAddress, Constants.ADDR_BROADCAST, true));
- return;
+ return Constants.HANDLED;
}
// Check if TV supports System Audio Control.
// Handle broadcasting setSystemAudioMode on or aborting message on callback.
@@ -983,6 +989,7 @@
}
}
});
+ return Constants.HANDLED;
}
void setTvSystemAudioModeSupport(boolean supported) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 2995252..10f6948f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -251,7 +251,8 @@
}
@ServiceThreadOnly
- protected boolean handleUserControlPressed(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleUserControlPressed(HdmiCecMessage message) {
assertRunOnServiceThread();
wakeUpIfActiveSource();
return super.handleUserControlPressed(message);
@@ -270,10 +271,11 @@
}
@ServiceThreadOnly
- protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!SET_MENU_LANGUAGE) {
- return false;
+ return Constants.ABORT_UNRECOGNIZED_OPCODE;
}
try {
@@ -283,7 +285,7 @@
// Do not switch language if the new language is the same as the current one.
// This helps avoid accidental country variant switching from en_US to en_AU
// due to the limitation of CEC. See the warning below.
- return true;
+ return Constants.HANDLED;
}
// Don't use Locale.getAvailableLocales() since it returns a locale
@@ -298,36 +300,38 @@
// will always be mapped to en-AU among other variants like en-US, en-GB,
// an en-IN, which may not be the expected one.
LocalePicker.updateLocale(localeInfo.getLocale());
- return true;
+ return Constants.HANDLED;
}
}
Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
- return false;
+ return Constants.ABORT_INVALID_OPERAND;
} catch (UnsupportedEncodingException e) {
Slog.w(TAG, "Can't handle <Set Menu Language>", e);
- return false;
+ return Constants.ABORT_INVALID_OPERAND;
}
}
@Override
- protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSetSystemAudioMode(HdmiCecMessage message) {
// System Audio Mode only turns on/off when Audio System broadcasts on/off message.
// For device with type 4 and 5, it can set system audio mode on/off
// when there is another audio system device connected into the system first.
if (message.getDestination() != Constants.ADDR_BROADCAST
|| message.getSource() != Constants.ADDR_AUDIO_SYSTEM
|| mService.audioSystem() != null) {
- return true;
+ return Constants.HANDLED;
}
boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
mService.setSystemAudioActivated(setSystemAudioModeOn);
}
- return true;
+ return Constants.HANDLED;
}
@Override
- protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
// Only directly addressed System Audio Mode Status message can change internal
// system audio mode status.
if (message.getDestination() == mAddress
@@ -337,25 +341,27 @@
mService.setSystemAudioActivated(setSystemAudioModeOn);
}
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleRoutingChange(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRoutingChange(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
handleRoutingChangeAndInformation(physicalAddress, message);
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleRoutingInformation(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRoutingInformation(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
handleRoutingChangeAndInformation(physicalAddress, message);
- return true;
+ return Constants.HANDLED;
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 2ed8481..979a1d4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -203,7 +203,8 @@
}
@ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
int logicalAddress = message.getSource();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
@@ -215,20 +216,22 @@
if (isRoutingControlFeatureEnabled()) {
switchInputOnReceivingNewActivePath(physicalAddress);
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRequestActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
maySendActiveSource(message.getSource());
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleSetStreamPath(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSetStreamPath(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
// If current device is the target path, set to Active Source.
@@ -242,12 +245,13 @@
setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleSetStreamPath()");
}
switchInputOnReceivingNewActivePath(physicalAddress);
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleRoutingChange(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRoutingChange(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) {
@@ -256,16 +260,16 @@
setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingChange()");
}
if (!isRoutingControlFeatureEnabled()) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
- return true;
+ return Constants.ABORT_REFUSED;
}
handleRoutingChangeAndInformation(physicalAddress, message);
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleRoutingInformation(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRoutingInformation(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) {
@@ -274,11 +278,10 @@
setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingInformation()");
}
if (!isRoutingControlFeatureEnabled()) {
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
- return true;
+ return Constants.ABORT_REFUSED;
}
handleRoutingChangeAndInformation(physicalAddress, message);
- return true;
+ return Constants.HANDLED;
}
// Method to switch Input with the new Active Path.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 90d6433..cd66a8f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -47,6 +47,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -210,11 +211,13 @@
@Override
@ServiceThreadOnly
- boolean dispatchMessage(HdmiCecMessage message) {
+ @VisibleForTesting
+ @Constants.HandleMessageResult
+ protected int dispatchMessage(HdmiCecMessage message) {
assertRunOnServiceThread();
if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived()
&& mStandbyHandler.handleCommand(message)) {
- return true;
+ return Constants.HANDLED;
}
return super.onMessage(message);
}
@@ -409,7 +412,8 @@
@Override
@ServiceThreadOnly
- protected boolean handleActiveSource(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
int logicalAddress = message.getSource();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
@@ -429,21 +433,22 @@
HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
mDelayedMessageBuffer.add(message);
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleInactiveSource(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleInactiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
// Seq #10
// Ignore <Inactive Source> from non-active source device.
if (getActiveSource().logicalAddress != message.getSource()) {
- return true;
+ return Constants.HANDLED;
}
if (isProhibitMode()) {
- return true;
+ return Constants.HANDLED;
}
int portId = getPrevPortId();
if (portId != Constants.INVALID_PORT_ID) {
@@ -452,10 +457,10 @@
HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo(
message.getSource());
if (inactiveSource == null) {
- return true;
+ return Constants.HANDLED;
}
if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
- return true;
+ return Constants.HANDLED;
}
// TODO: Switch the TV freeze mode off
@@ -468,29 +473,31 @@
setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRequestActiveSource(HdmiCecMessage message) {
assertRunOnServiceThread();
// Seq #19
if (mAddress == getActiveSource().logicalAddress) {
mService.sendCecCommand(
HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleGetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!broadcastMenuLanguage(mService.getLanguage())) {
Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
}
- return true;
+ return Constants.HANDLED;
}
@ServiceThreadOnly
@@ -506,7 +513,8 @@
}
@Override
- protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleReportPhysicalAddress(HdmiCecMessage message) {
super.handleReportPhysicalAddress(message);
int path = HdmiUtils.twoBytesToInt(message.getParams());
int address = message.getSource();
@@ -516,19 +524,21 @@
handleNewDeviceAtTheTailOfActivePath(path);
}
startNewDeviceAction(ActiveSource.of(address, path), type);
- return true;
+ return Constants.HANDLED;
}
@Override
- protected boolean handleTimerStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleTimerStatus(HdmiCecMessage message) {
// Do nothing.
- return true;
+ return Constants.HANDLED;
}
@Override
- protected boolean handleRecordStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRecordStatus(HdmiCecMessage message) {
// Do nothing.
- return true;
+ return Constants.HANDLED;
}
void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
@@ -590,7 +600,8 @@
@Override
@ServiceThreadOnly
- protected boolean handleRoutingChange(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRoutingChange(HdmiCecMessage message) {
assertRunOnServiceThread();
// Seq #21
byte[] params = message.getParams();
@@ -601,27 +612,29 @@
int newPath = HdmiUtils.twoBytesToInt(params, 2);
addAndStartAction(new RoutingControlAction(this, newPath, true, null));
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleReportAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
if (mService.getHdmiCecVolumeControl()
== HdmiControlManager.VOLUME_CONTROL_DISABLED) {
- return false;
+ return Constants.ABORT_REFUSED;
}
boolean mute = HdmiUtils.isAudioStatusMute(message);
int volume = HdmiUtils.getAudioStatusVolume(message);
setAudioStatus(mute, volume);
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleTextViewOn(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleTextViewOn(HdmiCecMessage message) {
assertRunOnServiceThread();
// Note that <Text View On> (and <Image View On>) command won't be handled here in
@@ -634,12 +647,13 @@
if (mService.isPowerStandbyOrTransient() && getAutoWakeup()) {
mService.wakeUp();
}
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleImageViewOn(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleImageViewOn(HdmiCecMessage message) {
assertRunOnServiceThread();
// Currently, it's the same as <Text View On>.
return handleTextViewOn(message);
@@ -977,7 +991,8 @@
@Override
@ServiceThreadOnly
- protected boolean handleInitiateArc(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleInitiateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!canStartArcUpdateAction(message.getSource(), true)) {
@@ -985,13 +1000,12 @@
if (avrDeviceInfo == null) {
// AVR may not have been discovered yet. Delay the message processing.
mDelayedMessageBuffer.add(message);
- return true;
+ return Constants.HANDLED;
}
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) {
displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
}
- return true;
+ return Constants.ABORT_REFUSED;
}
// In case where <Initiate Arc> is started by <Request ARC Initiation>
@@ -1000,7 +1014,7 @@
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
message.getSource(), true);
addAndStartAction(action);
- return true;
+ return Constants.HANDLED;
}
private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
@@ -1022,11 +1036,12 @@
@Override
@ServiceThreadOnly
- protected boolean handleTerminateArc(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleTerminateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
if (mService .isPowerStandbyOrTransient()) {
setArcStatus(false);
- return true;
+ return Constants.HANDLED;
}
// Do not check ARC configuration since the AVR might have been already removed.
// Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
@@ -1035,12 +1050,13 @@
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
message.getSource(), false);
addAndStartAction(action);
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSetSystemAudioMode(HdmiCecMessage message) {
assertRunOnServiceThread();
boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
if (!isMessageForSystemAudio(message)) {
@@ -1049,30 +1065,29 @@
mDelayedMessageBuffer.add(message);
} else {
HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+ return Constants.ABORT_REFUSED;
}
- return true;
} else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
+ "because the System Audio Control feature is disabled: %s", message);
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
- return true;
+ return Constants.ABORT_REFUSED;
}
removeAction(SystemAudioAutoInitiationAction.class);
SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
message.getSource(), systemAudioStatus, null);
addAndStartAction(action);
- return true;
+ return Constants.HANDLED;
}
@Override
@ServiceThreadOnly
- protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!isMessageForSystemAudio(message)) {
HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
// Ignore this message.
- return true;
+ return Constants.HANDLED;
}
boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled();
boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message);
@@ -1089,13 +1104,14 @@
setSystemAudioMode(tvSystemAudioMode);
}
- return true;
+ return Constants.HANDLED;
}
// Seq #53
@Override
@ServiceThreadOnly
- protected boolean handleRecordTvScreen(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleRecordTvScreen(HdmiCecMessage message) {
List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
if (!actions.isEmpty()) {
// Assumes only one OneTouchRecordAction.
@@ -1107,25 +1123,21 @@
}
// The default behavior of <Record TV Screen> is replying <Feature Abort> with
// "Cannot provide source".
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
- return true;
+ return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
}
int recorderAddress = message.getSource();
byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
- int reason = startOneTouchRecord(recorderAddress, recordSource);
- if (reason != Constants.ABORT_NO_ERROR) {
- mService.maySendFeatureAbortCommand(message, reason);
- }
- return true;
+ return startOneTouchRecord(recorderAddress, recordSource);
}
@Override
- protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleTimerClearedStatus(HdmiCecMessage message) {
byte[] params = message.getParams();
int timerClearedStatusData = params[0] & 0xFF;
announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
- return true;
+ return Constants.HANDLED;
}
void announceOneTouchRecordResult(int recorderAddress, int result) {
@@ -1337,6 +1349,7 @@
// Seq #54 and #55
@ServiceThreadOnly
+ @Constants.HandleMessageResult
int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
assertRunOnServiceThread();
if (!mService.isControlEnabled()) {
@@ -1362,7 +1375,7 @@
addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
+ Arrays.toString(recordSource));
- return Constants.ABORT_NO_ERROR;
+ return Constants.HANDLED;
}
@ServiceThreadOnly
@@ -1494,9 +1507,10 @@
}
@Override
- protected boolean handleMenuStatus(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleMenuStatus(HdmiCecMessage message) {
// Do nothing and just return true not to prevent from responding <Feature Abort>.
- return true;
+ return Constants.HANDLED;
}
@Constants.RcProfile
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 03a8338..031c057 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -372,8 +372,7 @@
private HdmiCecMessageValidator mMessageValidator;
- private final HdmiCecPowerStatusController mPowerStatusController =
- new HdmiCecPowerStatusController(this);
+ private HdmiCecPowerStatusController mPowerStatusController;
@ServiceThreadOnly
private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@@ -427,7 +426,7 @@
// Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing.
private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter();
- private CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer(this);
+ private CecMessageBuffer mCecMessageBuffer;
private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
@@ -493,6 +492,9 @@
mIoLooper = mIoThread.getLooper();
}
+ if (mPowerStatusController == null) {
+ mPowerStatusController = new HdmiCecPowerStatusController(this);
+ }
mPowerStatusController.setPowerStatus(getInitialPowerStatus());
mProhibitMode = false;
mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
@@ -501,6 +503,9 @@
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
+ if (mCecMessageBuffer == null) {
+ mCecMessageBuffer = new CecMessageBuffer(this);
+ }
if (mCecController == null) {
mCecController = HdmiCecController.create(this, getAtomWriter());
}
@@ -948,11 +953,10 @@
/**
* Returns {@link Looper} for IO operation.
- *
- * <p>Declared as package-private.
*/
@Nullable
- Looper getIoLooper() {
+ @VisibleForTesting
+ protected Looper getIoLooper() {
return mIoLooper;
}
@@ -974,10 +978,9 @@
/**
* Returns {@link Looper} of main thread. Use this {@link Looper} instance
* for tasks that are running on main service thread.
- *
- * <p>Declared as package-private.
*/
- Looper getServiceLooper() {
+ @VisibleForTesting
+ protected Looper getServiceLooper() {
return mHandler.getLooper();
}
@@ -1015,8 +1018,9 @@
/**
* Returns version of CEC.
*/
+ @VisibleForTesting
@HdmiControlManager.HdmiCecVersion
- int getCecVersion() {
+ protected int getCecVersion() {
return mCecVersion;
}
@@ -1087,23 +1091,30 @@
}
@ServiceThreadOnly
- boolean handleCecCommand(HdmiCecMessage message) {
+ @VisibleForTesting
+ @Constants.HandleMessageResult
+ protected int handleCecCommand(HdmiCecMessage message) {
assertRunOnServiceThread();
int errorCode = mMessageValidator.isValid(message);
if (errorCode != HdmiCecMessageValidator.OK) {
// We'll not response on the messages with the invalid source or destination
// or with parameter length shorter than specified in the standard.
if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
- maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
+ return Constants.ABORT_INVALID_OPERAND;
}
- return true;
+ return Constants.HANDLED;
}
getHdmiCecNetwork().handleCecMessage(message);
- if (dispatchMessageToLocalDevice(message)) {
- return true;
+
+ @Constants.HandleMessageResult int handleMessageResult =
+ dispatchMessageToLocalDevice(message);
+ if (handleMessageResult == Constants.NOT_HANDLED
+ && !mAddressAllocated
+ && mCecMessageBuffer.bufferMessage(message)) {
+ return Constants.HANDLED;
}
- return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false;
+ return handleMessageResult;
}
void enableAudioReturnChannel(int portId, boolean enabled) {
@@ -1111,19 +1122,25 @@
}
@ServiceThreadOnly
- private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
+ @VisibleForTesting
+ @Constants.HandleMessageResult
+ protected int dispatchMessageToLocalDevice(HdmiCecMessage message) {
assertRunOnServiceThread();
for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
- if (device.dispatchMessage(message)
+ @Constants.HandleMessageResult int messageResult = device.dispatchMessage(message);
+ if (messageResult != Constants.NOT_HANDLED
&& message.getDestination() != Constants.ADDR_BROADCAST) {
- return true;
+ return messageResult;
}
}
- if (message.getDestination() != Constants.ADDR_BROADCAST) {
+ // We should never respond <Feature Abort> to a broadcast message
+ if (message.getDestination() == Constants.ADDR_BROADCAST) {
+ return Constants.HANDLED;
+ } else {
HdmiLogger.warning("Unhandled cec command:" + message);
+ return Constants.NOT_HANDLED;
}
- return false;
}
/**
@@ -2970,7 +2987,7 @@
}
@VisibleForTesting
- boolean isStandbyMessageReceived() {
+ protected boolean isStandbyMessageReceived() {
return mStandbyMessageReceived;
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 0925027..0f13741 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -76,6 +76,8 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.VibrationEffect;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -1917,9 +1919,9 @@
}
private static class VibrationInfo {
- private long[] mPattern = new long[0];
- private int[] mAmplitudes = new int[0];
- private int mRepeat = -1;
+ private final long[] mPattern;
+ private final int[] mAmplitudes;
+ private final int mRepeat;
public long[] getPattern() {
return mPattern;
@@ -1934,40 +1936,55 @@
}
VibrationInfo(VibrationEffect effect) {
- // First replace prebaked effects with its fallback, if any available.
- if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect fallback = ((VibrationEffect.Prebaked) effect).getFallbackEffect();
- if (fallback != null) {
- effect = fallback;
+ long[] pattern = null;
+ int[] amplitudes = null;
+ int patternRepeatIndex = -1;
+ int amplitudeCount = -1;
+
+ if (effect instanceof VibrationEffect.Composed) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ int segmentCount = composed.getSegments().size();
+ pattern = new long[segmentCount];
+ amplitudes = new int[segmentCount];
+ patternRepeatIndex = composed.getRepeatIndex();
+ amplitudeCount = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if (composed.getRepeatIndex() == i) {
+ patternRepeatIndex = amplitudeCount;
+ }
+ if (!(segment instanceof StepSegment)) {
+ Slog.w(TAG, "Input devices don't support segment " + segment);
+ amplitudeCount = -1;
+ break;
+ }
+ float amplitude = ((StepSegment) segment).getAmplitude();
+ if (Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+ amplitudes[amplitudeCount] = DEFAULT_VIBRATION_MAGNITUDE;
+ } else {
+ amplitudes[amplitudeCount] =
+ (int) (amplitude * VibrationEffect.MAX_AMPLITUDE);
+ }
+ pattern[amplitudeCount++] = segment.getDuration();
}
}
- if (effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- mPattern = new long[] { 0, oneShot.getDuration() };
- int amplitude = oneShot.getAmplitude();
- // android framework uses DEFAULT_AMPLITUDE to signal that the vibration
- // should use some built-in default value, denoted here as
- // DEFAULT_VIBRATION_MAGNITUDE
- if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
- amplitude = DEFAULT_VIBRATION_MAGNITUDE;
- }
- mAmplitudes = new int[] { 0, amplitude };
+
+ if (amplitudeCount < 0) {
+ Slog.w(TAG, "Only oneshot and step waveforms are supported on input devices");
+ mPattern = new long[0];
+ mAmplitudes = new int[0];
mRepeat = -1;
- } else if (effect instanceof VibrationEffect.Waveform) {
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
- mPattern = waveform.getTimings();
- mAmplitudes = waveform.getAmplitudes();
- for (int i = 0; i < mAmplitudes.length; i++) {
- if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) {
- mAmplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE;
- }
- }
- mRepeat = waveform.getRepeatIndex();
- if (mRepeat >= mPattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
} else {
- Slog.w(TAG, "Pre-baked and composed effects aren't supported on input devices");
+ mRepeat = patternRepeatIndex;
+ mPattern = new long[amplitudeCount];
+ mAmplitudes = new int[amplitudeCount];
+ System.arraycopy(pattern, 0, mPattern, 0, amplitudeCount);
+ System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudeCount);
+ if (mRepeat >= mPattern.length) {
+ throw new ArrayIndexOutOfBoundsException("Repeat index " + mRepeat
+ + " must be within the bounds of the pattern.length "
+ + mPattern.length);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d17c24c..c9364c6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -17,6 +17,7 @@
import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
@@ -175,11 +176,9 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -199,6 +198,7 @@
import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerService;
+import com.android.server.utils.PriorityDump;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -1566,7 +1566,7 @@
LocalServices.addService(InputMethodManagerInternal.class,
new LocalServiceImpl(mService));
publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,
- DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
+ DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@Override
@@ -3200,7 +3200,7 @@
boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
mShowRequested = true;
- if (mAccessibilityRequestingNoSoftKeyboard) {
+ if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
return false;
}
@@ -4101,7 +4101,6 @@
*/
@BinderThread
@Override
- @GuardedBy("mMethodMap")
public void startProtoDump(byte[] protoDump, int source, String where,
IVoidResultCallback resultCallback) {
CallbackUtils.onResult(resultCallback, () -> {
@@ -4198,7 +4197,6 @@
});
}
- @GuardedBy("mMethodMap")
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (mMethodMap) {
final long token = proto.start(fieldId);
@@ -5227,26 +5225,71 @@
}
}
+ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (asProto) {
+ dumpAsProtoNoCheck(fd);
+ } else {
+ dumpAsStringNoCheck(fd, pw, args, true /* isCritical */);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ dumpNormal(fd, pw, args, asProto);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ if (asProto) {
+ dumpAsProtoNoCheck(fd);
+ } else {
+ dumpAsStringNoCheck(fd, pw, args, false /* isCritical */);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ dumpNormal(fd, pw, args, asProto);
+ }
+
+ @BinderThread
+ private void dumpAsProtoNoCheck(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+ proto.flush();
+ }
+ };
+
+ @BinderThread
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- if (ArrayUtils.contains(args, PROTO_ARG)) {
- final ImeTracing imeTracing = ImeTracing.getInstance();
- if (imeTracing.isEnabled()) {
- imeTracing.stopTrace(null, false /* writeToFile */);
- BackgroundThread.getHandler().post(() -> {
- imeTracing.writeTracesToFiles();
- imeTracing.startTrace(null);
- });
- }
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ }
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
- dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
- proto.flush();
- return;
- }
-
+ @BinderThread
+ private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean isCritical) {
IInputMethod method;
ClientState client;
ClientState focusedWindowClient;
@@ -5310,6 +5353,11 @@
mSoftInputShowHideHistory.dump(pw, " ");
}
+ // Exit here for critical dump, as remaining sections require IPCs to other processes.
+ if (isCritical) {
+ return;
+ }
+
p.println(" ");
if (client != null) {
pw.flush();
@@ -5818,7 +5866,7 @@
}
/**
- * Handles {@code adb shell ime tracing start/stop}.
+ * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
* @param shellCommand {@link ShellCommand} object that is handling this command.
* @return Exit code of the command.
*/
@@ -5830,16 +5878,19 @@
switch (cmd) {
case "start":
ImeTracing.getInstance().getInstance().startTrace(pw);
- break;
+ break; // proceed to the next step to update the IME client processes.
case "stop":
ImeTracing.getInstance().stopTrace(pw);
- break;
+ break; // proceed to the next step to update the IME client processes.
+ case "save-for-bugreport":
+ ImeTracing.getInstance().saveForBugreport(pw);
+ return ShellCommandResult.SUCCESS; // no need to update the IME client processes.
default:
pw.println("Unknown command: " + cmd);
pw.println("Input method trace options:");
pw.println(" start: Start tracing");
pw.println(" stop: Stop tracing");
- return ShellCommandResult.FAILURE;
+ return ShellCommandResult.FAILURE; // no need to update the IME client processes.
}
boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
ArrayMap<IBinder, ClientState> clients;
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index c93c4b1..dc3596b 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
import java.util.Collections;
@@ -54,9 +55,11 @@
private final ServiceWatcher mServiceWatcher;
private GeocoderProxy(Context context) {
- mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION, null, null,
- com.android.internal.R.bool.config_enableGeocoderOverlay,
- com.android.internal.R.string.config_geocoderProviderPackageName);
+ mServiceWatcher = ServiceWatcher.create(context, "GeocoderProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+ com.android.internal.R.bool.config_enableGeocoderOverlay,
+ com.android.internal.R.string.config_geocoderProviderPackageName),
+ null);
}
private boolean register() {
@@ -72,7 +75,7 @@
*/
public void getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
@@ -97,7 +100,7 @@
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
index e1c8700..6ac6e77 100644
--- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
+++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
@@ -25,15 +25,15 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
/**
* Proxy class to bind GmsCore to the ActivityRecognitionHardware.
- *
- * @hide
*/
-public class HardwareActivityRecognitionProxy {
+public class HardwareActivityRecognitionProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "ARProxy";
private static final String SERVICE_ACTION =
@@ -66,12 +66,16 @@
mInstance = null;
}
- mServiceWatcher = new ServiceWatcher(context,
- SERVICE_ACTION,
- this::onBind,
- null,
- com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
- com.android.internal.R.string.config_activityRecognitionHardwarePackageName);
+ int useOverlayResId =
+ com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay;
+ int nonOverlayPackageResId =
+ com.android.internal.R.string.config_activityRecognitionHardwarePackageName;
+
+ mServiceWatcher = ServiceWatcher.create(context,
+ "HardwareActivityRecognitionProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION, useOverlayResId,
+ nonOverlayPackageResId),
+ this);
}
private boolean register() {
@@ -82,7 +86,8 @@
return resolves;
}
- private void onBind(IBinder binder, BoundService service) throws RemoteException {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
String descriptor = binder.getInterfaceDescriptor();
if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
@@ -99,4 +104,7 @@
Log.e(TAG, "Unknown descriptor: " + descriptor);
}
}
+
+ @Override
+ public void onUnbind() {}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 6d1606d..864aa33 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -378,6 +378,7 @@
// provider has unfortunate hard dependencies on the network provider
ProxyLocationProvider networkProvider = ProxyLocationProvider.create(
mContext,
+ NETWORK_PROVIDER,
ACTION_NETWORK_PROVIDER,
com.android.internal.R.bool.config_enableNetworkLocationOverlay,
com.android.internal.R.string.config_networkLocationProviderPackageName);
@@ -397,6 +398,7 @@
ProxyLocationProvider fusedProvider = ProxyLocationProvider.create(
mContext,
+ FUSED_PROVIDER,
ACTION_FUSED_PROVIDER,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName);
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
index c707149..90b446e 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
@@ -29,14 +29,17 @@
import android.os.UserHandle;
import android.util.Log;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
/**
* @hide
*/
-public final class GeofenceProxy {
+public final class GeofenceProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "GeofenceProxy";
private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider";
@@ -62,10 +65,12 @@
private GeofenceProxy(Context context, IGpsGeofenceHardware gpsGeofence) {
mGpsGeofenceHardware = Objects.requireNonNull(gpsGeofence);
- mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION,
- (binder, service) -> updateGeofenceHardware(binder), null,
- com.android.internal.R.bool.config_enableGeofenceOverlay,
- com.android.internal.R.string.config_geofenceProviderPackageName);
+ mServiceWatcher = ServiceWatcher.create(context,
+ "GeofenceProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+ com.android.internal.R.bool.config_enableGeofenceOverlay,
+ com.android.internal.R.string.config_geofenceProviderPackageName),
+ this);
mGeofenceHardware = null;
}
@@ -87,6 +92,14 @@
return resolves;
}
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
+ updateGeofenceHardware(binder);
+ }
+
+ @Override
+ public void onUnbind() {}
+
private class GeofenceProxyServiceConnection implements ServiceConnection {
GeofenceProxyServiceConnection() {}
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 317e61b..5df7870 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -19,7 +19,6 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.Context;
import android.location.Location;
import android.location.LocationResult;
@@ -28,33 +27,34 @@
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
import com.android.server.FgThread;
import com.android.server.location.provider.AbstractLocationProvider;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
/**
* Proxy for ILocationProvider implementations.
*/
-public class ProxyLocationProvider extends AbstractLocationProvider {
+public class ProxyLocationProvider extends AbstractLocationProvider implements
+ ServiceListener<BoundServiceInfo> {
- private static final String KEY_EXTRA_ATTRIBUTION_TAGS = "android:location_allow_listed_tags";
- private static final String EXTRA_ATTRIBUTION_TAGS_SEPARATOR = ";";
+ private static final String EXTRA_LOCATION_TAGS = "android:location_allow_listed_tags";
+ private static final String LOCATION_TAGS_SEPARATOR = ";";
private static final long RESET_DELAY_MS = 1000;
@@ -63,10 +63,10 @@
* null.
*/
@Nullable
- public static ProxyLocationProvider create(Context context, String action,
+ public static ProxyLocationProvider create(Context context, String provider, String action,
int enableOverlayResId, int nonOverlayPackageResId) {
- ProxyLocationProvider proxy = new ProxyLocationProvider(context, action, enableOverlayResId,
- nonOverlayPackageResId);
+ ProxyLocationProvider proxy = new ProxyLocationProvider(context, provider, action,
+ enableOverlayResId, nonOverlayPackageResId);
if (proxy.checkServiceResolves()) {
return proxy;
} else {
@@ -84,23 +84,23 @@
@GuardedBy("mLock")
@Nullable Runnable mResetter;
-
@GuardedBy("mLock")
- Proxy mProxy;
+ @Nullable Proxy mProxy;
@GuardedBy("mLock")
- @Nullable ComponentName mService;
+ @Nullable BoundServiceInfo mBoundServiceInfo;
private volatile ProviderRequest mRequest;
- private ProxyLocationProvider(Context context, String action, int enableOverlayResId,
- int nonOverlayPackageResId) {
+ private ProxyLocationProvider(Context context, String provider, String action,
+ int enableOverlayResId, int nonOverlayPackageResId) {
// safe to use direct executor since our locks are not acquired in a code path invoked by
// our owning provider
super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
mContext = context;
- mServiceWatcher = new ServiceWatcher(context, action, this::onBind,
- this::onUnbind, enableOverlayResId, nonOverlayPackageResId);
+ mServiceWatcher = ServiceWatcher.create(context, provider,
+ new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+ nonOverlayPackageResId), this);
mProxy = null;
mRequest = ProviderRequest.EMPTY_REQUEST;
@@ -110,26 +110,13 @@
return mServiceWatcher.checkServiceResolves();
}
- private void onBind(IBinder binder, BoundService boundService) throws RemoteException {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
synchronized (mLock) {
mProxy = new Proxy();
- mService = boundService.component;
-
- if (mResetter != null) {
- FgThread.getHandler().removeCallbacks(mResetter);
- mResetter = null;
- }
-
- // update extra attribution tag info from manifest
- if (boundService.metadata != null) {
- String tagsList = boundService.metadata.getString(KEY_EXTRA_ATTRIBUTION_TAGS);
- if (tagsList != null) {
- setExtraAttributionTags(
- new ArraySet<>(tagsList.split(EXTRA_ATTRIBUTION_TAGS_SEPARATOR)));
- }
- }
+ mBoundServiceInfo = boundServiceInfo;
provider.setLocationProviderManager(mProxy);
@@ -140,26 +127,29 @@
}
}
- private void onUnbind() {
+ @Override
+ public void onUnbind() {
Runnable[] flushListeners;
synchronized (mLock) {
mProxy = null;
- mService = null;
+ mBoundServiceInfo = null;
// we need to clear the state - but most disconnections are very temporary. we give a
// grace period where we don't clear the state immediately so that transient
- // interruptions are not visible to clients
- mResetter = new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- if (mResetter == this) {
- setState(prevState -> State.EMPTY_STATE);
+ // interruptions are not necessarily visible to downstream clients
+ if (mResetter == null) {
+ mResetter = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mResetter == this) {
+ setState(prevState -> State.EMPTY_STATE);
+ }
}
}
- }
- };
- FgThread.getHandler().postDelayed(mResetter, RESET_DELAY_MS);
+ };
+ FgThread.getHandler().postDelayed(mResetter, RESET_DELAY_MS);
+ }
flushListeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
@@ -192,7 +182,7 @@
@Override
protected void onFlush(Runnable callback) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
@@ -232,20 +222,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mServiceWatcher.dump(fd, pw, args);
- }
-
- private static String guessPackageName(Context context, int uid, String packageName) {
- String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
- if (packageNames == null || packageNames.length == 0) {
- // illegal state exception will propagate back through binders
- throw new IllegalStateException(
- "location provider from uid " + uid + " has no package information");
- } else if (ArrayUtils.contains(packageNames, packageName)) {
- return packageName;
- } else {
- return packageNames[0];
- }
+ mServiceWatcher.dump(pw);
}
private class Proxy extends ILocationProviderManager.Stub {
@@ -255,27 +232,37 @@
// executed on binder thread
@Override
public void onInitialize(boolean allowed, ProviderProperties properties,
- @Nullable String packageName, @Nullable String attributionTag) {
+ @Nullable String attributionTag) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
- CallerIdentity identity;
- if (packageName == null) {
- packageName = guessPackageName(mContext, Binder.getCallingUid(),
- Objects.requireNonNull(mService).getPackageName());
- // unsafe is ok since the package is coming direct from the package manager here
- identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
- } else {
- identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+ if (mResetter != null) {
+ FgThread.getHandler().removeCallbacks(mResetter);
+ mResetter = null;
}
+ // set extra attribution tags from manifest if necessary
+ String[] attributionTags = new String[0];
+ if (mBoundServiceInfo.getMetadata() != null) {
+ String tagsStr = mBoundServiceInfo.getMetadata().getString(EXTRA_LOCATION_TAGS);
+ if (!TextUtils.isEmpty(tagsStr)) {
+ attributionTags = tagsStr.split(LOCATION_TAGS_SEPARATOR);
+ }
+ }
+ ArraySet<String> extraAttributionTags = new ArraySet<>(attributionTags);
+
+ // unsafe is ok since we trust the package name already
+ CallerIdentity identity = CallerIdentity.fromBinderUnsafe(
+ mBoundServiceInfo.getComponentName().getPackageName(),
+ attributionTag);
+
setState(prevState -> State.EMPTY_STATE
- .withExtraAttributionTags(prevState.extraAttributionTags)
.withAllowed(allowed)
.withProperties(properties)
- .withIdentity(identity));
+ .withIdentity(identity)
+ .withExtraAttributionTags(extraAttributionTags));
}
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 6e99cba..76ecc1a 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -15,14 +15,17 @@
*/
package com.android.server.locksettings;
+
import static android.os.UserHandle.USER_SYSTEM;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -35,6 +38,8 @@
import com.android.internal.widget.RebootEscrowListener;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -65,6 +70,22 @@
public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count";
static final String REBOOT_ESCROW_KEY_ARMED_TIMESTAMP = "reboot_escrow_key_stored_timestamp";
+ static final String REBOOT_ESCROW_KEY_PROVIDER = "reboot_escrow_key_provider";
+
+ /**
+ * The verified boot 2.0 vbmeta digest of the current slot, the property value is always
+ * available after boot.
+ */
+ static final String VBMETA_DIGEST_PROP_NAME = "ro.boot.vbmeta.digest";
+ /**
+ * The system prop contains vbmeta digest of the inactive slot. The build property is set after
+ * an OTA update. RebootEscrowManager will store it in disk before the OTA reboot, so the value
+ * is available for vbmeta digest verification after the device reboots.
+ */
+ static final String OTHER_VBMETA_DIGEST_PROP_NAME = "ota.other.vbmeta_digest";
+ static final String REBOOT_ESCROW_KEY_VBMETA_DIGEST = "reboot_escrow_key_vbmeta_digest";
+ static final String REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST =
+ "reboot_escrow_key_other_vbmeta_digest";
/**
* Number of boots until we consider the escrow data to be stale for the purposes of metrics.
@@ -86,6 +107,31 @@
private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;
+ @IntDef(prefix = {"ERROR_"}, value = {
+ ERROR_NONE,
+ ERROR_UNKNOWN,
+ ERROR_NO_PROVIDER,
+ ERROR_LOAD_ESCROW_KEY,
+ ERROR_RETRY_COUNT_EXHAUSTED,
+ ERROR_UNLOCK_ALL_USERS,
+ ERROR_PROVIDER_MISMATCH,
+ ERROR_KEYSTORE_FAILURE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RebootEscrowErrorCode {
+ }
+
+ static final int ERROR_NONE = 0;
+ static final int ERROR_UNKNOWN = 1;
+ static final int ERROR_NO_PROVIDER = 2;
+ static final int ERROR_LOAD_ESCROW_KEY = 3;
+ static final int ERROR_RETRY_COUNT_EXHAUSTED = 4;
+ static final int ERROR_UNLOCK_ALL_USERS = 5;
+ static final int ERROR_PROVIDER_MISMATCH = 6;
+ static final int ERROR_KEYSTORE_FAILURE = 7;
+
+ private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;
+
/**
* Logs events for later debugging in bugreports.
*/
@@ -199,6 +245,10 @@
0);
}
+ public long getCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
public int getLoadEscrowDataRetryLimit() {
return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
"load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
@@ -221,6 +271,11 @@
public RebootEscrowEventLog getEventLog() {
return new RebootEscrowEventLog();
}
+
+ public String getVbmetaDigest(boolean other) {
+ return other ? SystemProperties.get(OTHER_VBMETA_DIGEST_PROP_NAME)
+ : SystemProperties.get(VBMETA_DIGEST_PROP_NAME);
+ }
}
RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
@@ -261,6 +316,7 @@
if (rebootEscrowUsers.isEmpty()) {
Slog.i(TAG, "No reboot escrow data found for users,"
+ " skipping loading escrow data");
+ clearMetricsStorage();
return;
}
@@ -284,6 +340,7 @@
}
Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+ mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED;
onGetRebootEscrowKeyFailed(users, attemptNumber);
}
@@ -307,6 +364,17 @@
}
if (escrowKey == null) {
+ if (mLoadEscrowDataErrorCode == ERROR_NONE) {
+ // Specifically check if the RoR provider has changed after reboot.
+ int providerType = mInjector.serverBasedResumeOnReboot()
+ ? RebootEscrowProviderInterface.TYPE_SERVER_BASED
+ : RebootEscrowProviderInterface.TYPE_HAL;
+ if (providerType != mStorage.getInt(REBOOT_ESCROW_KEY_PROVIDER, -1, USER_SYSTEM)) {
+ mLoadEscrowDataErrorCode = ERROR_PROVIDER_MISMATCH;
+ } else {
+ mLoadEscrowDataErrorCode = ERROR_LOAD_ESCROW_KEY;
+ }
+ }
onGetRebootEscrowKeyFailed(users, attemptNumber + 1);
return;
}
@@ -321,9 +389,49 @@
// Clear the old key in keystore. A new key will be generated by new RoR requests.
mKeyStoreManager.clearKeyStoreEncryptionKey();
+ if (!allUsersUnlocked && mLoadEscrowDataErrorCode == ERROR_NONE) {
+ mLoadEscrowDataErrorCode = ERROR_UNLOCK_ALL_USERS;
+ }
onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1);
}
+ private void clearMetricsStorage() {
+ mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
+ mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM);
+ mStorage.removeKey(REBOOT_ESCROW_KEY_VBMETA_DIGEST, USER_SYSTEM);
+ mStorage.removeKey(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, USER_SYSTEM);
+ mStorage.removeKey(REBOOT_ESCROW_KEY_PROVIDER, USER_SYSTEM);
+ }
+
+ private int getVbmetaDigestStatusOnRestoreComplete() {
+ String currentVbmetaDigest = mInjector.getVbmetaDigest(false);
+ String vbmetaDigestStored = mStorage.getString(REBOOT_ESCROW_KEY_VBMETA_DIGEST,
+ "", USER_SYSTEM);
+ String vbmetaDigestOtherStored = mStorage.getString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST,
+ "", USER_SYSTEM);
+
+ // The other vbmeta digest is never set, assume no slot switch is attempted.
+ if (vbmetaDigestOtherStored.isEmpty()) {
+ if (currentVbmetaDigest.equals(vbmetaDigestStored)) {
+ return FrameworkStatsLog
+ .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
+ }
+ return FrameworkStatsLog
+ .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH;
+ }
+
+ // The other vbmeta digest is set, we expect to boot into the new slot.
+ if (currentVbmetaDigest.equals(vbmetaDigestOtherStored)) {
+ return FrameworkStatsLog
+ .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
+ } else if (currentVbmetaDigest.equals(vbmetaDigestStored)) {
+ return FrameworkStatsLog
+ .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_FALLBACK_SLOT;
+ }
+ return FrameworkStatsLog
+ .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH;
+ }
+
private void reportMetricOnRestoreComplete(boolean success, int attemptCount) {
int serviceType = mInjector.serverBasedResumeOnReboot()
? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED
@@ -331,26 +439,32 @@
long armedTimestamp = mStorage.getLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, -1,
USER_SYSTEM);
- mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM);
- int escrowDurationInSeconds = armedTimestamp != -1
- ? (int) (System.currentTimeMillis() - armedTimestamp) / 1000 : -1;
+ int escrowDurationInSeconds = -1;
+ long currentTimeStamp = mInjector.getCurrentTimeMillis();
+ if (armedTimestamp != -1 && currentTimeStamp > armedTimestamp) {
+ escrowDurationInSeconds = (int) (currentTimeStamp - armedTimestamp) / 1000;
+ }
- // TODO(b/179105110) design error code; and report the true value for other fields.
- int vbmetaDigestStatus = FrameworkStatsLog
- .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
+ int vbmetaDigestStatus = getVbmetaDigestStatusOnRestoreComplete();
+ if (!success && mLoadEscrowDataErrorCode == ERROR_NONE) {
+ mLoadEscrowDataErrorCode = ERROR_UNKNOWN;
+ }
- mInjector.reportMetric(success, 0 /* error code */, serviceType, attemptCount,
+ // TODO(179105110) report the duration since boot complete.
+ mInjector.reportMetric(success, mLoadEscrowDataErrorCode, serviceType, attemptCount,
escrowDurationInSeconds, vbmetaDigestStatus, -1);
+
+ mLoadEscrowDataErrorCode = ERROR_NONE;
}
private void onEscrowRestoreComplete(boolean success, int attemptCount) {
int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM);
- mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
int bootCountDelta = mInjector.getBootCount() - previousBootCount;
if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) {
reportMetricOnRestoreComplete(success, attemptCount);
}
+ clearMetricsStorage();
}
private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
@@ -358,6 +472,14 @@
if (rebootEscrowProvider == null) {
Slog.w(TAG,
"Had reboot escrow data for users, but RebootEscrowProvider is unavailable");
+ mLoadEscrowDataErrorCode = ERROR_NO_PROVIDER;
+ return null;
+ }
+
+ // Server based RoR always need the decryption key from keystore.
+ if (rebootEscrowProvider.getType() == RebootEscrowProviderInterface.TYPE_SERVER_BASED
+ && kk == null) {
+ mLoadEscrowDataErrorCode = ERROR_KEYSTORE_FAILURE;
return null;
}
@@ -463,7 +585,7 @@
return;
}
- mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
+ clearMetricsStorage();
rebootEscrowProvider.clearRebootEscrowKey();
List<UserInfo> users = mUserManager.getUsers();
@@ -486,6 +608,9 @@
return false;
}
+ int actualProviderType = rebootEscrowProvider.getType();
+ // TODO(b/183140900) Fail the reboot if provider type mismatches.
+
RebootEscrowKey escrowKey;
synchronized (mKeyGenerationLock) {
escrowKey = mPendingRebootEscrowKey;
@@ -505,8 +630,14 @@
boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk);
if (armedRebootEscrow) {
mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
- mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, System.currentTimeMillis(),
+ mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(),
USER_SYSTEM);
+ // Store the vbmeta digest of both slots.
+ mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false),
+ USER_SYSTEM);
+ mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST,
+ mInjector.getVbmetaDigest(true), USER_SYSTEM);
+ mStorage.setInt(REBOOT_ESCROW_KEY_PROVIDER, actualProviderType, USER_SYSTEM);
mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS);
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
index 4b00772..e8f6f4a 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
@@ -60,6 +60,11 @@
}
@Override
+ public int getType() {
+ return TYPE_HAL;
+ }
+
+ @Override
public boolean hasRebootEscrowSupport() {
return mInjector.getRebootEscrow() != null;
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
index af6faad..e106d81 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
@@ -16,7 +16,11 @@
package com.android.server.locksettings;
+import android.annotation.IntDef;
+
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import javax.crypto.SecretKey;
@@ -28,6 +32,21 @@
* @hide
*/
public interface RebootEscrowProviderInterface {
+ @IntDef(prefix = {"TYPE_"}, value = {
+ TYPE_HAL,
+ TYPE_SERVER_BASED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RebootEscrowProviderType {
+ }
+ int TYPE_HAL = 0;
+ int TYPE_SERVER_BASED = 1;
+
+ /**
+ * Returns the reboot escrow provider type.
+ */
+ @RebootEscrowProviderType int getType();
+
/**
* Returns true if the secure store/discard of reboot escrow key is supported.
*/
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index 697bf08..2866987 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -95,6 +95,11 @@
}
@Override
+ public int getType() {
+ return TYPE_SERVER_BASED;
+ }
+
+ @Override
public boolean hasRebootEscrowSupport() {
return mInjector.getServiceConnection() != null;
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index db2e908..a30d993 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1225,6 +1225,70 @@
}
@Override
+ public MediaSession.Token getMediaKeyEventSession() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " get the media key event session");
+ }
+ MediaSessionRecordImpl record;
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "No matching user record to get the media key event session"
+ + ", userId=" + userId);
+ return null;
+ }
+ record = user.getMediaButtonSessionLocked();
+ }
+ if (record instanceof MediaSessionRecord) {
+ return ((MediaSessionRecord) record).getSessionToken();
+ }
+ //TODO: Handle media session 2 case
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public String getMediaKeyEventSessionPackageName() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " get the media key event session package");
+ }
+ MediaSessionRecordImpl record;
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "No matching user record to get the media key event session"
+ + " package , userId=" + userId);
+ return "";
+ }
+ record = user.getMediaButtonSessionLocked();
+ if (record instanceof MediaSessionRecord) {
+ return record.getPackageName();
+ //TODO: Handle media session 2 case
+ } else if (user.mLastMediaButtonReceiverHolder != null) {
+ return user.mLastMediaButtonReceiverHolder.getPackageName();
+ }
+ }
+ return "";
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void addSessionsListener(IActiveSessionsListener listener,
ComponentName componentName, int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 619fc4e..dc3b78a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -33,6 +33,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -73,7 +74,6 @@
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
@@ -184,6 +184,8 @@
private Map<String, List<String>> mOemLockedApps = new HashMap();
+ private int mCurrentUserId = UserHandle.USER_NULL;
+
public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager,
@@ -199,7 +201,8 @@
updateBadgingEnabled();
updateBubblesEnabled();
updateMediaNotificationFilteringEnabled();
- syncChannelsBypassingDnd(mContext.getUserId());
+ mCurrentUserId = ActivityManager.getCurrentUser();
+ syncChannelsBypassingDnd();
}
public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
@@ -806,7 +809,7 @@
// but the system can
if (group.isBlocked() != oldGroup.isBlocked()) {
group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
}
}
@@ -888,13 +891,13 @@
// fields on the channel yet
if (existing.getUserLockedFields() == 0 && hasDndAccess) {
boolean bypassDnd = channel.canBypassDnd();
- if (bypassDnd != existing.canBypassDnd()) {
+ if (bypassDnd != existing.canBypassDnd() || wasUndeleted) {
existing.setBypassDnd(bypassDnd);
needsPolicyFileChange = true;
if (bypassDnd != mAreChannelsBypassingDnd
|| previousExistingImportance != existing.getImportance()) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
}
}
@@ -958,7 +961,7 @@
r.channels.put(channel.getId(), channel);
if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
MetricsLogger.action(getChannelLog(channel, pkg).setType(
com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -1047,7 +1050,7 @@
if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
|| channel.getImportance() != updatedChannel.getImportance()) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
}
updateConfig();
@@ -1145,7 +1148,7 @@
mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg);
if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
}
}
@@ -1512,7 +1515,7 @@
}
}
if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
return deletedChannelIds;
}
@@ -1658,29 +1661,29 @@
}
/**
- * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
+ * Syncs {@link #mAreChannelsBypassingDnd} with the current user's notification policy before
* updating
- * @param userId
*/
- private void syncChannelsBypassingDnd(int userId) {
+ private void syncChannelsBypassingDnd() {
mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
& NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
- updateChannelsBypassingDnd(userId);
+ updateChannelsBypassingDnd();
}
/**
- * Updates the user's NotificationPolicy based on whether the given userId
+ * Updates the user's NotificationPolicy based on whether the current userId
* has channels bypassing DND
* @param userId
*/
- private void updateChannelsBypassingDnd(int userId) {
+ private void updateChannelsBypassingDnd() {
synchronized (mPackagePreferences) {
final int numPackagePreferences = mPackagePreferences.size();
for (int i = 0; i < numPackagePreferences; i++) {
final PackagePreferences r = mPackagePreferences.valueAt(i);
- // Package isn't associated with this userId or notifications from this package are
- // blocked
- if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+ // Package isn't associated with the current userId or notifications from this
+ // package are blocked
+ if (mCurrentUserId != UserHandle.getUserId(r.uid)
+ || r.importance == IMPORTANCE_NONE) {
continue;
}
@@ -2226,14 +2229,16 @@
* Called when user switches
*/
public void onUserSwitched(int userId) {
- syncChannelsBypassingDnd(userId);
+ mCurrentUserId = userId;
+ syncChannelsBypassingDnd();
}
/**
* Called when user is unlocked
*/
public void onUserUnlocked(int userId) {
- syncChannelsBypassingDnd(userId);
+ mCurrentUserId = userId;
+ syncChannelsBypassingDnd();
}
public void onUserRemoved(int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 7aaab0c..e0a39f3 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -260,7 +260,8 @@
// Only report metrics for base apk for now.
// TODO: add ISA and APK type to metrics.
- if (pkg.getBaseApkPath().equals(path)) {
+ // OTAPreopt doesn't have stats so don't report in that case.
+ if (pkg.getBaseApkPath().equals(path) && packageStats != null) {
Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
try {
long sessionId = Math.randomLongInternal();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index bafe987..67638bc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1212,12 +1212,18 @@
mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
+ MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
} else {
- // For incremental installs, continue publishing the install progress during committing.
- mProgress = mIncrementalProgress;
+ // For incremental install, continue to publish incremental progress during committing.
+ if (isIncrementalInstallation() && (mIncrementalProgress - mProgress) >= 0.01) {
+ // It takes some time for data loader to write to incremental file system, so at the
+ // beginning of the commit, the incremental progress might be very small.
+ // Wait till the incremental progress is larger than what's already displayed.
+ // This way we don't see the progress ring going backwards.
+ mProgress = mIncrementalProgress;
+ }
}
// Only publish when meaningful change
- if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
+ if (forcePublish || (mProgress - mReportedProgress) >= 0.01) {
mReportedProgress = mProgress;
mCallback.onSessionProgressChanged(this, mProgress);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b27c0bd..764fa02 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -252,7 +252,6 @@
import android.content.pm.parsing.component.ParsedProcess;
import android.content.pm.parsing.component.ParsedProvider;
import android.content.pm.parsing.component.ParsedService;
-import android.content.pm.parsing.component.ParsedUsesPermission;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Resources;
@@ -371,6 +370,7 @@
import com.android.server.SystemServerInitThreadPool;
import com.android.server.Watchdog;
import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.apphibernation.AppHibernationService;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -778,16 +778,6 @@
private static final String COMPANION_PACKAGE_NAME = "com.android.companiondevicemanager";
- /** Canonical intent used to identify what counts as a "web browser" app */
- private static final Intent sBrowserIntent;
- static {
- sBrowserIntent = new Intent();
- sBrowserIntent.setAction(Intent.ACTION_VIEW);
- sBrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE);
- sBrowserIntent.setData(Uri.parse("http:"));
- sBrowserIntent.addFlags(Intent.FLAG_IGNORE_EPHEMERAL);
- }
-
// Compilation reasons.
public static final int REASON_UNKNOWN = -1;
public static final int REASON_FIRST_BOOT = 0;
@@ -5636,7 +5626,7 @@
// Work that needs to happen on first install within each user
if (firstUserIds != null && firstUserIds.length > 0) {
for (int userId : firstUserIds) {
- clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+ restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
pkgSetting.getInstallReason(userId), userId);
}
}
@@ -7752,19 +7742,6 @@
return matches.get(0).getComponentInfo().getComponentName();
}
- private boolean packageIsBrowser(String packageName, int userId) {
- List<ResolveInfo> list = queryIntentActivitiesInternal(sBrowserIntent, null,
- PackageManager.MATCH_ALL, userId);
- final int N = list.size();
- for (int i = 0; i < N; i++) {
- ResolveInfo info = list.get(i);
- if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
- return true;
- }
- }
- return false;
- }
-
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -12304,9 +12281,15 @@
public ArraySet<String> getOptimizablePackages() {
ArraySet<String> pkgs = new ArraySet<>();
+ final boolean hibernationEnabled = AppHibernationService.isAppHibernationEnabled();
+ AppHibernationManagerInternal appHibernationManager =
+ mInjector.getLocalService(AppHibernationManagerInternal.class);
synchronized (mLock) {
for (AndroidPackage p : mPackages.values()) {
- if (PackageDexOptimizer.canOptimizePackage(p)) {
+ // Checking hibernation state is an inexpensive call.
+ boolean isHibernating = hibernationEnabled
+ && appHibernationManager.isHibernatingGlobally(p.getPackageName());
+ if (PackageDexOptimizer.canOptimizePackage(p) && !isHibernating) {
pkgs.add(p.getPackageName());
}
}
@@ -15459,16 +15442,55 @@
null);
}
- private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
boolean suspended) {
- final Bundle extras = new Bundle(3);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
- sendPackageBroadcast(
- suspended ? Intent.ACTION_PACKAGES_SUSPENDED
- : Intent.ACTION_PACKAGES_UNSUSPENDED,
- null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
- new int[] {userId}, null, null, null);
+ final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
+ final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
+ final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
+ final int[] userIds = new int[] {userId};
+ // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
+ // allow lists are the same.
+ synchronized (mLock) {
+ for (int i = 0; i < pkgList.length; i++) {
+ final String pkgName = pkgList[i];
+ final int uid = uidList[i];
+ SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList(
+ getPackageSettingInternal(pkgName, Process.SYSTEM_UID),
+ userIds, mSettings.getPackagesLocked());
+ if (allowList == null) {
+ allowList = new SparseArray<>(0);
+ }
+ boolean merged = false;
+ for (int j = 0; j < allowListsToSend.size(); j++) {
+ if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
+ pkgsToSend.get(j).add(pkgName);
+ uidsToSend.get(j).add(uid);
+ merged = true;
+ break;
+ }
+ }
+ if (!merged) {
+ pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
+ uidsToSend.add(IntArray.wrap(new int[] {uid}));
+ allowListsToSend.add(allowList);
+ }
+ }
+ }
+
+ for (int i = 0; i < pkgsToSend.size(); i++) {
+ final Bundle extras = new Bundle(3);
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+ pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
+ final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
+ ? null : allowListsToSend.get(i);
+ sendPackageBroadcast(
+ suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+ : Intent.ACTION_PACKAGES_UNSUSPENDED,
+ null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
+ userIds, null, allowList, null);
+ }
}
/**
@@ -15612,7 +15634,7 @@
PostInstallData postInstallData =
new PostInstallData(null, res, () -> {
- clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+ restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
pkgSetting.getInstallReason(userId), userId);
if (intentSender != null) {
onRestoreComplete(res.returnCode, mContext, intentSender);
@@ -21182,7 +21204,6 @@
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mLock) {
mDomainVerificationManager.clearPackage(deletedPs.name);
- clearDefaultBrowserIfNeeded(packageName);
mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
mAppsFilter.removePackage(getPackageSetting(packageName));
removedAppId = mSettings.removePackageLPw(packageName);
@@ -21740,7 +21761,6 @@
destroyAppDataLIF(pkg, nextUserId,
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
}
- clearDefaultBrowserIfNeededForUser(ps.name, nextUserId);
removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), nextUserId, ps.appId);
clearPackagePreferredActivities(ps.name, nextUserId);
mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs,
@@ -22257,36 +22277,8 @@
mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId);
}
- /** Clears state for all users, and touches intent filter verification policy */
- void clearDefaultBrowserIfNeeded(String packageName) {
- for (int oneUserId : mUserManager.getUserIds()) {
- clearDefaultBrowserIfNeededForUser(packageName, oneUserId);
- }
- }
-
- private void clearDefaultBrowserIfNeededForUser(String packageName, int userId) {
- final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(userId);
- if (!TextUtils.isEmpty(defaultBrowserPackageName)) {
- if (packageName.equals(defaultBrowserPackageName)) {
- mDefaultAppProvider.setDefaultBrowser(null, true, userId);
- }
- }
- }
-
- private void clearRolesAndRestorePermissionsForNewUserInstall(String packageName,
+ private void restorePermissionsAndUpdateRolesForNewUserInstall(String packageName,
int installReason, @UserIdInt int userId) {
- // If this app is a browser and it's newly-installed for some
- // users, clear any default-browser state in those users. The
- // app's nature doesn't depend on the user, so we can just check
- // its browser nature in any user and generalize.
- if (packageIsBrowser(packageName, userId)) {
- // If this browser is restored from user's backup, do not clear
- // default-browser state for this user
- if (installReason != PackageManager.INSTALL_REASON_DEVICE_RESTORE) {
- mDefaultAppProvider.setDefaultBrowser(null, true, userId);
- }
- }
-
// We may also need to apply pending (restored) runtime permission grants
// within these users.
mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);
@@ -22320,11 +22312,6 @@
}
}
updateDefaultHomeNotLocked(userId);
- // TODO: We have to reset the default SMS and Phone. This requires
- // significant refactoring to keep all default apps in the package
- // manager (cleaner but more work) or have the services provide
- // callbacks to the package manager to request a default app reset.
- mDefaultAppProvider.setDefaultBrowser(null, true, userId);
resetNetworkPolicies(userId);
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
@@ -27329,6 +27316,11 @@
return PackageManagerService.this.getPackageStartability(
packageName, callingUid, userId) == PACKAGE_STARTABILITY_FROZEN;
}
+
+ @Override
+ public void deleteOatArtifactsOfPackage(String packageName) {
+ PackageManagerService.this.deleteOatArtifactsOfPackage(packageName);
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index b5765b5..0ddb6cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -104,8 +104,8 @@
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
-import com.android.server.pm.verify.domain.DomainVerificationShell;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationShell;
import dalvik.system.DexFile;
@@ -2251,8 +2251,8 @@
}
}
- final String packageName = getNextArg();
- if (packageName == null) {
+ final List<String> packageNames = getRemainingArgs();
+ if (packageNames.isEmpty()) {
pw.println("Error: package name not specified");
return 1;
}
@@ -2270,12 +2270,15 @@
try {
final int translatedUserId =
translateUserId(userId, UserHandle.USER_NULL, "runSuspend");
- mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
- ((appExtras.size() > 0) ? appExtras : null),
+ mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}),
+ suspendedState, ((appExtras.size() > 0) ? appExtras : null),
((launcherExtras.size() > 0) ? launcherExtras : null),
info, callingPackage, translatedUserId);
- pw.println("Package " + packageName + " new suspended state: "
- + mInterface.isPackageSuspendedForUser(packageName, translatedUserId));
+ for (int i = 0; i < packageNames.size(); i++) {
+ final String packageName = packageNames.get(i);
+ pw.println("Package " + packageName + " new suspended state: "
+ + mInterface.isPackageSuspendedForUser(packageName, translatedUserId));
+ }
return 0;
} catch (RemoteException | IllegalArgumentException e) {
pw.println(e.toString());
@@ -3643,11 +3646,11 @@
pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println("");
- pw.println(" suspend [--user USER_ID] TARGET-PACKAGE");
- pw.println(" Suspends the specified package (as user).");
+ pw.println(" suspend [--user USER_ID] PACKAGE [PACKAGE...]");
+ pw.println(" Suspends the specified package(s) (as user).");
pw.println("");
- pw.println(" unsuspend [--user USER_ID] TARGET-PACKAGE");
- pw.println(" Unsuspends the specified package (as user).");
+ pw.println(" unsuspend [--user USER_ID] PACKAGE [PACKAGE...]");
+ pw.println(" Unsuspends the specified package(s) (as user).");
pw.println("");
pw.println(" grant [--user USER_ID] PACKAGE PERMISSION");
pw.println(" revoke [--user USER_ID] PACKAGE PERMISSION");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 302e657..8c3c423 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,8 +20,17 @@
import android.annotation.UserIdInt;
import android.app.Person;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+import android.app.appsearch.ReportUsageRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.content.ComponentName;
import android.content.Intent;
@@ -36,6 +45,7 @@
import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.PersistableBundle;
+import android.os.StrictMode;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -48,6 +58,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -70,13 +81,15 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -145,9 +158,9 @@
private static final String KEY_BITMAP_BYTES = "bitmapBytes";
/**
- * All the shortcuts from the package, keyed on IDs.
+ * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
- final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+ final ArraySet<ShortcutInfo> mShortcuts = new ArraySet<>();
/**
* All the share targets from the package
@@ -207,7 +220,9 @@
}
public int getShortcutCount() {
- return mShortcuts.size();
+ final int[] count = new int[1];
+ forEachShortcut(si -> count[0]++);
+ return count[0];
}
@Override
@@ -221,17 +236,20 @@
// - Unshadow all shortcuts.
// - Set disabled reason.
// - Disable if needed.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_SHADOW);
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasFlags(ShortcutInfo.FLAG_SHADOW)
+ && si.getDisabledReason() == restoreBlockReason
+ && restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ return false;
+ }
+ si.clearFlags(ShortcutInfo.FLAG_SHADOW);
- shortcut.setDisabledReason(restoreBlockReason);
- if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
- shortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
- }
- });
- }
+ si.setDisabledReason(restoreBlockReason);
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.addFlags(ShortcutInfo.FLAG_DISABLED);
+ }
+ return true;
+ });
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags();
@@ -242,7 +260,7 @@
*/
@Nullable
public ShortcutInfo findShortcutById(String id) {
- return mShortcuts.get(id);
+ return getShortcutById(id);
}
public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -265,7 +283,7 @@
}
public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
- ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
+ ensureNotImmutable(findShortcutById(id), ignoreInvisible);
}
public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
@@ -308,8 +326,9 @@
* Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
+ final ShortcutInfo shortcut = getShortcutById(id);
if (shortcut != null) {
+ removeShortcut(id);
mShortcutUser.mService.removeIconLocked(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
| ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
@@ -326,10 +345,10 @@
forceDeleteShortcutInner(newShortcut.getId());
- // Extract Icon and update the icon res ID and the bitmap path.
s.saveIconAndFixUpShortcutLocked(newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
- mShortcuts.put(newShortcut.getId(), newShortcut);
+
+ saveShortcut(newShortcut);
}
/**
@@ -347,7 +366,7 @@
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
if (oldShortcut != null) {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
@@ -379,7 +398,7 @@
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
changedShortcuts.clear();
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
boolean deleted = false;
if (oldShortcut == null) {
@@ -418,6 +437,16 @@
}
forceReplaceShortcutInner(newShortcut);
+ // TODO: Report usage can be filed async
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.reportUsage(
+ new ReportUsageRequest.Builder(getPackageName())
+ .setUri(newShortcut.getId()).build(),
+ mShortcutUser.mExecutor, result -> future.complete(result.isSuccess()));
+ return future;
+ });
+
return deleted;
}
@@ -427,19 +456,12 @@
* @return List of removed shortcuts.
*/
private List<ShortcutInfo> removeOrphans() {
- List<ShortcutInfo> removeList = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.isAlive()) continue;
-
- if (removeList == null) {
- removeList = new ArrayList<>();
- }
+ final List<ShortcutInfo> removeList = new ArrayList<>(1);
+ forEachShortcut(si -> {
+ if (si.isAlive()) return;
removeList.add(si);
- }
- if (removeList != null) {
+ });
+ if (!removeList.isEmpty()) {
for (int i = removeList.size() - 1; i >= 0; i--) {
forceDeleteShortcutInner(removeList.get(i).getId());
}
@@ -456,20 +478,19 @@
public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
- boolean changed = false;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final boolean[] changed = new boolean[1];
+ forEachShortcutMutateIf(si -> {
if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
- changed = true;
+ changed[0] = true;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- shortcut.setRank(0); // It may still be pinned, so clear the rank.
- });
+ si.setTimestamp(now);
+ si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ si.setRank(0); // It may still be pinned, so clear the rank.
+ return true;
}
- }
- if (changed) {
+ return false;
+ });
+ if (changed[0]) {
return removeOrphans();
}
return null;
@@ -508,7 +529,7 @@
* @return The deleted shortcut, or null if it was not actually removed because it's pinned.
*/
public ShortcutInfo deleteLongLivedWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
- final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
+ final ShortcutInfo shortcut = findShortcutById(shortcutId);
if (shortcut != null) {
mutateShortcut(shortcutId, null, si -> si.clearFlags(ShortcutInfo.FLAG_CACHED_ALL));
}
@@ -551,7 +572,7 @@
Preconditions.checkState(
(disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
"disable and disabledReason disagree: " + disable + " vs " + disabledReason);
- final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
+ final ShortcutInfo oldShortcut = findShortcutById(shortcutId);
if (oldShortcut == null || !oldShortcut.isEnabled()
&& (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
@@ -567,7 +588,7 @@
si.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
si.addFlags(ShortcutInfo.FLAG_DISABLED);
- // Do not overwrite the disabled reason if one is alreay set.
+ // Do not overwrite the disabled reason if one is already set.
if (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
si.setDisabledReason(disabledReason);
}
@@ -579,7 +600,6 @@
si.setActivity(null);
}
});
-
return null;
} else {
forceDeleteShortcutInner(shortcutId);
@@ -596,7 +616,7 @@
}
public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
- final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+ final ShortcutInfo source = findShortcutById(shortcut.getId());
Objects.requireNonNull(source);
mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
@@ -615,13 +635,6 @@
* <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
*/
public void refreshPinnedFlags() {
- // TODO: rewrite this function with proper query (i.e. fetch only pinned shortcuts and
- // unpin if it's no longer pinned by any launcher and vice versa)
- final List<ShortcutInfo> shortcuts = new ArrayList<>(mShortcuts.values());
- final Map<String, ShortcutInfo> shortcutMap = new ArrayMap<>(shortcuts.size());
- for (ShortcutInfo si : shortcuts) {
- shortcutMap.put(si.getId(), si);
- }
final Set<String> pinnedShortcuts = new ArraySet<>();
// First, for the pinned set for each launcher, keep track of their id one by one.
@@ -631,31 +644,20 @@
if (pinned == null || pinned.size() == 0) {
return;
}
- for (int i = pinned.size() - 1; i >= 0; i--) {
- final String id = pinned.valueAt(i);
- final ShortcutInfo si = shortcutMap.get(id);
- if (si == null) {
- // This happens if a launcher pinned shortcuts from this package, then backup&
- // restored, but this package doesn't allow backing up.
- // In that case the launcher ends up having a dangling pinned shortcuts.
- // That's fine, when the launcher is restored, we'll fix it.
- continue;
- }
- pinnedShortcuts.add(si.getId());
- }
+ pinnedShortcuts.addAll(pinned);
});
// Then, update the pinned state if necessary
- for (int i = shortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = shortcuts.get(i);
+ forEachShortcutMutateIf(si -> {
if (pinnedShortcuts.contains(si.getId()) && !si.isPinned()) {
- mutateShortcut(si.getId(), si,
- shortcut -> shortcut.addFlags(ShortcutInfo.FLAG_PINNED));
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) {
- mutateShortcut(si.getId(), si, shortcut ->
- shortcut.clearFlags(ShortcutInfo.FLAG_PINNED));
+ si.clearFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
- }
+ return false;
+ });
// Lastly, remove the ones that are no longer pinned, cached nor dynamic.
removeOrphans();
@@ -764,17 +766,13 @@
// Restored and the app not installed yet, so don't return any.
return;
}
-
final ShortcutService s = mShortcutUser.mService;
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
- .getPinnedShortcutIds(getPackageName(), getPackageUserId());
-
- for (int i = 0; i < mShortcuts.size(); i++) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ .getPinnedShortcutIds(getPackageName(), getPackageUserId());
+ forEachShortcut(si -> {
// Need to adjust PINNED flag depending on the caller.
// Basically if the caller is a launcher (callingLauncher != null) and the launcher
// isn't pinning it, then we need to clear PINNED for this caller.
@@ -784,7 +782,7 @@
if (!getPinnedByAnyLauncher) {
if (si.isFloating() && !si.isCached()) {
if (!isPinnedByCaller) {
- continue;
+ return;
}
}
}
@@ -804,7 +802,7 @@
}
result.add(clone);
}
- }
+ });
}
public void resetThrottling() {
@@ -874,7 +872,7 @@
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) {
+ if (getShortcutCount() == 0 || mShareTargets.isEmpty()) {
return 0;
}
@@ -912,14 +910,12 @@
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
public ArraySet<String> getUsedBitmapFiles() {
- final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final ArraySet<String> usedFiles = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.getBitmapPath() != null) {
usedFiles.add(getFileName(si.getBitmapPath()));
}
- }
+ });
return usedFiles;
}
@@ -936,30 +932,29 @@
* @return false if any of the target activities are no longer enabled.
*/
private boolean areAllActivitiesStillEnabled() {
- if (mShortcuts.size() == 0) {
- return true;
- }
final ShortcutService s = mShortcutUser.mService;
// Normally the number of target activities is 1 or so, so no need to use a complex
// structure like a set.
final ArrayList<ComponentName> checked = new ArrayList<>(4);
+ final boolean[] reject = new boolean[1];
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutStopWhen(si -> {
final ComponentName activity = si.getActivity();
if (checked.contains(activity)) {
- continue; // Already checked.
+ return false; // Already checked.
}
checked.add(activity);
if ((activity != null)
&& !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
- return false;
+ reject[0] = true;
+ return true; // Found at least 1 activity is disabled, so skip the rest.
}
- }
- return true;
+ return false;
+ });
+ return !reject[0];
}
/**
@@ -1042,33 +1037,32 @@
// See if there are any shortcuts that were prevented restoring because the app was of a
// lower version, and re-enable them.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
- continue;
+ return false;
}
if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
si.getId(), getPackageInfo().getBackupSourceVersionCode()));
}
- continue;
+ return false;
}
Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
- shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
- });
- }
+ if (si.hasFlags(ShortcutInfo.FLAG_DISABLED)
+ || si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+ return true;
+ }
+ return false;
+ });
// For existing shortcuts, update timestamps if they have any resources.
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
if (!isNewApp) {
- Resources publisherRes = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ final Resources publisherRes = getPackageResources();
+ forEachShortcutMutateIf(si -> {
// Disable dynamic shortcuts whose target activity is gone.
if (si.isDynamic()) {
if (si.getActivity() == null) {
@@ -1081,33 +1075,26 @@
getPackageName(), si.getId()));
if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
ShortcutInfo.DISABLED_REASON_APP_CHANGED) != null) {
- continue; // Actually removed.
+ return false; // Actually removed.
}
// Still pinned, so fall-through and possibly update the resources.
}
}
- if (si.hasAnyResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- if (!shortcut.isOriginallyFromManifest()) {
- shortcut.lookupAndFillInResourceIds(res);
- }
-
- // If this shortcut is not from a manifest, then update all resource IDs
- // from resource names. (We don't allow resource strings for
- // non-manifest at the moment, but icons can still be resources.)
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
+ if (!si.hasAnyResources() || publisherRes == null) {
+ return false;
}
- }
+
+ if (!si.isOriginallyFromManifest()) {
+ si.lookupAndFillInResourceIds(publisherRes);
+ }
+
+ // If this shortcut is not from a manifest, then update all resource IDs
+ // from resource names. (We don't allow resource strings for
+ // non-manifest at the moment, but icons can still be resources.)
+ si.setTimestamp(s.injectCurrentTimeMillis());
+ return true;
+ });
}
// (Re-)publish manifest shortcut.
@@ -1133,17 +1120,12 @@
boolean changed = false;
// Keep the previous IDs.
- ArraySet<String> toDisableList = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ final ArraySet<String> toDisableList = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.isManifestShortcut()) {
- if (toDisableList == null) {
- toDisableList = new ArraySet<>();
- }
toDisableList.add(si.getId());
}
- }
+ });
// Publish new ones.
if (newManifestShortcutList != null) {
@@ -1156,7 +1138,7 @@
final boolean newDisabled = !newShortcut.isEnabled();
final String id = newShortcut.getId();
- final ShortcutInfo oldShortcut = mShortcuts.get(id);
+ final ShortcutInfo oldShortcut = findShortcutById(id);
boolean wasPinned = false;
@@ -1183,7 +1165,7 @@
// regardless.
forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
- if (!newDisabled && toDisableList != null) {
+ if (!newDisabled && !toDisableList.isEmpty()) {
// Still alive, don't remove.
toDisableList.remove(id);
}
@@ -1191,7 +1173,7 @@
}
// Disable the previous manifest shortcuts that are no longer in the manifest.
- if (toDisableList != null) {
+ if (!toDisableList.isEmpty()) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format(
"Package %s: disabling %d stale shortcuts", getPackageName(),
@@ -1206,8 +1188,9 @@
/* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
ShortcutInfo.DISABLED_REASON_APP_CHANGED);
}
- removeOrphans();
}
+ removeOrphans();
+
adjustRanks();
return changed;
}
@@ -1279,25 +1262,21 @@
private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
= new ArrayMap<>();
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (si.isFloating()) {
- continue; // Ignore floating shortcuts, which are not tied to any activities.
+ return; // Ignore floating shortcuts, which are not tied to any activities.
}
final ComponentName activity = si.getActivity();
if (activity == null) {
mShortcutUser.mService.wtf("null activity detected.");
- continue;
+ return;
}
- ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
- if (list == null) {
- list = new ArrayList<>();
- activitiesToShortcuts.put(activity, list);
- }
+ ArrayList<ShortcutInfo> list = activitiesToShortcuts.computeIfAbsent(activity,
+ k -> new ArrayList<>());
list.add(si);
- }
+ });
return activitiesToShortcuts;
}
@@ -1333,15 +1312,13 @@
// (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
// anyway.)
final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo shortcut = mShortcuts.valueAt(i);
-
+ forEachShortcut(shortcut -> {
if (shortcut.isManifestShortcut()) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
} else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
}
- }
+ });
for (int i = newList.size() - 1; i >= 0; i--) {
final ShortcutInfo newShortcut = newList.get(i);
@@ -1354,7 +1331,7 @@
continue; // Activity can be null for update.
}
- final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo original = findShortcutById(newShortcut.getId());
if (original == null) {
if (operation == ShortcutService.OPERATION_UPDATE) {
continue; // When updating, ignore if there's no target.
@@ -1391,34 +1368,19 @@
* For all the text fields, refresh the string values if they're from resources.
*/
public void resolveResourceStrings() {
- // TODO: update resource strings in AppSearch
final ShortcutService s = mShortcutUser.mService;
- List<ShortcutInfo> changedShortcuts = null;
+ final Resources publisherRes = getPackageResources();
+ final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
- Resources publisherRes = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.hasStringResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.resolveResourceStrings(res);
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
-
- if (changedShortcuts == null) {
- changedShortcuts = new ArrayList<>(1);
- }
+ if (publisherRes != null) {
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasStringResources()) return false;
+ si.resolveResourceStrings(publisherRes);
+ si.setTimestamp(s.injectCurrentTimeMillis());
changedShortcuts.add(si);
- }
+ return true;
+ });
}
if (!CollectionUtils.isEmpty(changedShortcuts)) {
s.packageShortcutsChanged(getPackageName(), getPackageUserId(), changedShortcuts, null);
@@ -1427,10 +1389,7 @@
/** Clears the implicit ranks for all shortcuts. */
public void clearAllImplicitRanks() {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, ShortcutInfo::clearImplicitRankAndRankChangedFlag);
- }
+ forEachShortcutMutate(ShortcutInfo::clearImplicitRankAndRankChangedFlag);
}
/**
@@ -1470,17 +1429,16 @@
final long now = s.injectCurrentTimeMillis();
// First, clear ranks for floating shortcuts.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.isFloating()) {
if (si.getRank() != 0) {
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.setRank(0);
- });
+ si.setTimestamp(now);
+ si.setRank(0);
+ return true;
}
}
- }
+ return false;
+ });
// Then adjust ranks. Ranks are unique for each activity, so we first need to sort
// shortcuts to each activity.
@@ -1505,7 +1463,7 @@
}
// At this point, it must be dynamic.
if (!si.isDynamic()) {
- s.wtf("Non-dynamic shortcut found.");
+ s.wtf("Non-dynamic shortcut found.: " + si.toInsecureString());
continue;
}
final int thisRank = rank++;
@@ -1521,13 +1479,15 @@
/** @return true if there's any shortcuts that are not manifest shortcuts. */
public boolean hasNonManifestShortcuts() {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final boolean[] condition = new boolean[1];
+ forEachShortcutStopWhen(si -> {
if (!si.isDeclaredInManifest()) {
+ condition[0] = true;
return true;
}
- }
- return false;
+ return false;
+ });
+ return condition[0];
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
@@ -1567,11 +1527,8 @@
pw.print(prefix);
pw.println(" Shortcuts:");
- long totalBitmapSize = 0;
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ final long[] totalBitmapSize = new long[1];
+ forEachShortcut(si -> {
pw.println(si.toDumpString(prefix + " "));
if (si.getBitmapPath() != null) {
final long len = new File(si.getBitmapPath()).length();
@@ -1580,15 +1537,15 @@
pw.print("bitmap size=");
pw.println(len);
- totalBitmapSize += len;
+ totalBitmapSize[0] += len;
}
- }
+ });
pw.print(prefix);
pw.print(" ");
pw.print("Total bitmap size: ");
- pw.print(totalBitmapSize);
+ pw.print(totalBitmapSize[0]);
pw.print(" (");
- pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
+ pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
pw.println(")");
}
@@ -1603,46 +1560,39 @@
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
| (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ forEachShortcut(si -> {
if ((si.getFlags() & shortcutFlags) != 0) {
pw.println(si.toDumpString(""));
}
- }
+ });
}
@Override
public JSONObject dumpCheckin(boolean clear) throws JSONException {
final JSONObject result = super.dumpCheckin(clear);
- int numDynamic = 0;
- int numPinned = 0;
- int numManifest = 0;
- int numBitmaps = 0;
- long totalBitmapSize = 0;
+ final int[] numDynamic = new int[1];
+ final int[] numPinned = new int[1];
+ final int[] numManifest = new int[1];
+ final int[] numBitmaps = new int[1];
+ final long[] totalBitmapSize = new long[1];
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
-
- if (si.isDynamic()) numDynamic++;
- if (si.isDeclaredInManifest()) numManifest++;
- if (si.isPinned()) numPinned++;
+ forEachShortcut(si -> {
+ if (si.isDynamic()) numDynamic[0]++;
+ if (si.isDeclaredInManifest()) numManifest[0]++;
+ if (si.isPinned()) numPinned[0]++;
if (si.getBitmapPath() != null) {
- numBitmaps++;
- totalBitmapSize += new File(si.getBitmapPath()).length();
+ numBitmaps[0]++;
+ totalBitmapSize[0] += new File(si.getBitmapPath()).length();
}
- }
+ });
- result.put(KEY_DYNAMIC, numDynamic);
- result.put(KEY_MANIFEST, numManifest);
- result.put(KEY_PINNED, numPinned);
- result.put(KEY_BITMAPS, numBitmaps);
- result.put(KEY_BITMAP_BYTES, totalBitmapSize);
+ result.put(KEY_DYNAMIC, numDynamic[0]);
+ result.put(KEY_MANIFEST, numManifest[0]);
+ result.put(KEY_PINNED, numPinned[0]);
+ result.put(KEY_BITMAPS, numBitmaps[0]);
+ result.put(KEY_BITMAP_BYTES, totalBitmapSize[0]);
// TODO Log update frequency too.
@@ -1652,7 +1602,7 @@
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- final int size = mShortcuts.size();
+ final int size = getShortcutCount();
final int shareTargetSize = mShareTargets.size();
if (size == 0 && shareTargetSize == 0 && mApiCallCount == 0) {
@@ -1666,9 +1616,15 @@
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
- for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup,
- getPackageInfo().isBackupAllowed());
+ if (forBackup) {
+ // Shortcuts are persisted in AppSearch, xml is only needed for backup.
+ forEachShortcut(si -> {
+ try {
+ saveShortcut(out, si, forBackup, getPackageInfo().isBackupAllowed());
+ } catch (IOException | XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
if (!forBackup) {
@@ -1785,12 +1741,14 @@
}
final Intent[] intentsNoExtras = si.getIntentsNoExtras();
final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
- final int numIntents = intentsNoExtras.length;
- for (int i = 0; i < numIntents; i++) {
- out.startTag(null, TAG_INTENT);
- ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
- out.endTag(null, TAG_INTENT);
+ if (intentsNoExtras != null && intentsExtras != null) {
+ final int numIntents = intentsNoExtras.length;
+ for (int i = 0; i < numIntents; i++) {
+ out.startTag(null, TAG_INTENT);
+ ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+ out.endTag(null, TAG_INTENT);
+ }
}
ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
@@ -1877,9 +1835,8 @@
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName,
shortcutUser.getUserId(), fromBackup);
-
// Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
+ ret.mShortcuts.add(si);
continue;
case TAG_SHARE_TARGET:
ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
@@ -2075,7 +2032,9 @@
@VisibleForTesting
List<ShortcutInfo> getAllShortcutsForTest() {
- return new ArrayList<>(mShortcuts.values());
+ final List<ShortcutInfo> ret = new ArrayList<>(1);
+ forEachShortcut(ret::add);
+ return ret;
}
@VisibleForTesting
@@ -2087,7 +2046,7 @@
public void verifyStates() {
super.verifyStates();
- boolean failed = false;
+ final boolean[] failed = new boolean[1];
final ShortcutService s = mShortcutUser.mService;
@@ -2098,7 +2057,7 @@
for (int outer = all.size() - 1; outer >= 0; outer--) {
final ArrayList<ShortcutInfo> list = all.valueAt(outer);
if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
+ " has " + all.valueAt(outer).size() + " shortcuts.");
}
@@ -2118,61 +2077,60 @@
}
// Verify each shortcut's status.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned() || si.isCached())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not manifest, dynamic or pinned.");
}
if (si.isDeclaredInManifest() && si.isDynamic()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is both dynamic and manifest at the same time.");
}
if (si.getActivity() == null && !si.isFloating()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has null activity, but not floating.");
}
if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not floating, but is disabled.");
}
if (si.isFloating() && si.getRank() != 0) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is floating, but has rank=" + si.getRank());
}
if (si.getIcon() != null) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " still has an icon");
}
if (si.hasAdaptiveBitmap() && !(si.hasIconFile() || si.hasIconUri())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has adaptive bitmap but was not saved to a file nor has icon uri.");
}
if (si.hasIconFile() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both resource and bitmap icons");
}
if (si.hasIconFile() && si.hasIconUri()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and bitmap icons");
}
if (si.hasIconUri() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and resource icons");
}
if (si.isEnabled()
!= (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " isEnabled() and getDisabledReason() disagree: "
+ si.isEnabled() + " vs " + si.getDisabledReason());
@@ -2180,18 +2138,18 @@
if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
&& (getPackageInfo().getBackupSourceVersionCode()
== ShortcutInfo.VERSION_CODE_UNKNOWN)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " RESTORED_VERSION_LOWER with no backup source version code.");
}
if (s.isDummyMainActivity(si.getActivity())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has a dummy target activity");
}
- }
+ });
- if (failed) {
+ if (failed[0]) {
throw new IllegalStateException("See logcat for errors");
}
}
@@ -2202,7 +2160,7 @@
} else {
mPackageIdentifiers.remove(packageName);
}
- resetAppSearch(null);
+ resetAppSearch(session -> AndroidFuture.completedFuture(true));
}
void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
@@ -2212,23 +2170,282 @@
synchronized (mLock) {
if (shortcut != null) {
transform.accept(shortcut);
- } else {
- transform.accept(findShortcutById(id));
}
- // TODO: Load ShortcutInfo from AppSearch, apply transformation logic and save
+ final ShortcutInfo si = getShortcutById(id);
+ if (si == null) {
+ return;
+ }
+ transform.accept(si);
+ saveShortcut(si);
}
}
+ private void saveShortcut(@NonNull final ShortcutInfo... shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ saveShortcut(Arrays.asList(shortcuts));
+ }
+
+ private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ ConcurrentUtils.waitForFutureNoInterrupt(
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.put(new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ AppSearchShortcutInfo.toGenericDocuments(shortcuts))
+ .build(),
+ mShortcutUser.mExecutor,
+ result -> {
+ if (!result.isSuccess()) {
+ for (AppSearchResult<Void> k : result.getFailures().values()) {
+ Slog.e(TAG, k.getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "failed to save shortcuts"));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ }),
+ "saving shortcut");
+ }
+
/**
* Removes shortcuts from AppSearch.
*/
void removeShortcuts() {
+ awaitInAppSearch("removing shortcuts", session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove("",
+ new SearchSpec.Builder()
+ .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(new RuntimeException(
+ "Failed to cleanup shortcuts " + result.getErrorMessage()));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private void removeShortcut(@NonNull final String id) {
+ Objects.requireNonNull(id);
+ awaitInAppSearch("removing shortcut with id=" + id, session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove(new RemoveByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ final Map<String, AppSearchResult<Void>> failures =
+ result.getFailures();
+ for (String key : failures.keySet()) {
+ Slog.e(TAG, "Failed deleting " + key + ", error message:"
+ + failures.get(key).getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "Failed to delete shortcut: " + id));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private ShortcutInfo getShortcutById(String id) {
+ return awaitInAppSearch("getting shortcut with id=" + id, session -> {
+ final AndroidFuture<ShortcutInfo> future = new AndroidFuture<>();
+ session.getByUri(
+ new GetByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor,
+ results -> {
+ if (results.isSuccess()) {
+ Map<String, GenericDocument> documents = results.getSuccesses();
+ for (GenericDocument doc : documents.values()) {
+ final ShortcutInfo info = new AppSearchShortcutInfo(doc)
+ .toShortcutInfo(mShortcutUser.getUserId());
+ future.complete(info);
+ return;
+ }
+ }
+ future.complete(null);
+ });
+ return future;
+ });
+ }
+
+ private void forEachShortcut(
+ @NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutStopWhen(si -> {
+ cb.accept(si);
+ return false;
+ });
+ }
+
+ private void forEachShortcutMutate(@NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutMutateIf(si -> {
+ cb.accept(si);
+ return true;
+ });
+ }
+
+ private void forEachShortcutMutateIf(@NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("mutating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ final List<ShortcutInfo> changed = new ArrayList<>(1);
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) changed.add(si);
+ }
+ saveShortcut(changed);
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ forEachShortcutStopWhen("", cb);
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("iterating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search(query, new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) return;
+ }
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private List<ShortcutInfo> getNextPage(@NonNull final SearchResults res) {
+ final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
+ final List<ShortcutInfo> ret = new ArrayList<>();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ res.getNextPage(mShortcutUser.mExecutor, nextPage -> {
+ if (!nextPage.isSuccess()) {
+ future.complete(ret);
+ return;
+ }
+ final List<SearchResult> results = nextPage.getResultValue();
+ if (results.isEmpty()) {
+ future.complete(ret);
+ return;
+ }
+ final List<ShortcutInfo> page = new ArrayList<>(results.size());
+ for (SearchResult result : results) {
+ final ShortcutInfo si = new AppSearchShortcutInfo(result.getDocument())
+ .toShortcutInfo(mShortcutUser.getUserId());
+ page.add(si);
+ }
+ ret.addAll(page);
+ future.complete(ret);
+ });
+ return ConcurrentUtils.waitForFutureNoInterrupt(future,
+ "getting next batch of shortcuts");
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Nullable
+ private <T> T awaitInAppSearch(
+ @NonNull final String description,
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ return ConcurrentUtils.waitForFutureNoInterrupt(runInAppSearch(cb), description);
+ }
+
+ @Nullable
+ private <T> CompletableFuture<T> runInAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ synchronized (mLock) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
+ .build());
+ if (mAppSearchSession != null) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return AndroidFuture.supply(() -> mAppSearchSession).thenCompose(cb);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ } else {
+ return resetAppSearch(cb);
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+ }
+
+ private <T> CompletableFuture<T> resetAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ final AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder()
+ .setDatabaseName(getPackageName()).build();
+ final AppSearchSession session;
+ try {
+ session = ConcurrentUtils.waitForFutureNoInterrupt(
+ mShortcutUser.getAppSearch(searchContext), "resetting app search");
+ ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "setting up schema");
+ mAppSearchSession = session;
+ return cb.apply(mAppSearchSession);
+ } catch (Exception e) {
+ return AndroidFuture.completedFuture(null);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @NonNull
+ private AndroidFuture<AppSearchSession> setupSchema(
+ @NonNull final AppSearchSession session) {
+ SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
+ for (PackageIdentifier pi : mPackageIdentifiers.values()) {
+ schemaBuilder = schemaBuilder
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchPerson.SCHEMA_TYPE, true, pi)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
+ }
+ final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
+ session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new IllegalArgumentException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(session);
+ });
+ return future;
}
/**
* Merge/replace shortcuts parsed from xml file.
*/
void restoreParsedShortcuts(final boolean replace) {
+ if (replace) {
+ removeShortcuts();
+ }
+ saveShortcut(mShortcuts);
}
private boolean verifyRanksSequential(List<ShortcutInfo> list) {
@@ -2239,133 +2456,9 @@
if (si.getRank() != i) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
- + " rank=" + si.getRank() + " but expected to be "+ i);
+ + " rank=" + si.getRank() + " but expected to be " + i);
}
}
return failed;
}
-
- private void runInAppSearch(
- Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
- if (mShortcutUser == null) {
- Slog.w(TAG, "shortcut user is null");
- return;
- }
- synchronized (mLock) {
- if (mAppSearchSession != null) {
- final CountDownLatch latch = new CountDownLatch(1);
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- final SearchSessionObservable upstream =
- new SearchSessionObservable(mAppSearchSession, latch);
- for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
- : observers) {
- upstream.map(observer);
- }
- upstream.next();
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
- }
- ConcurrentUtils.waitForCountDownNoInterrupt(latch, 500,
- "timeout accessing shortcut");
- } else {
- resetAppSearch(observers);
- }
- }
- }
-
- private void resetAppSearch(
- Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
- final CountDownLatch latch = new CountDownLatch(1);
- final AppSearchManager.SearchContext searchContext =
- new AppSearchManager.SearchContext.Builder()
- .setDatabaseName(getPackageName()).build();
- mShortcutUser.runInAppSearch(searchContext, result -> {
- if (!result.isSuccess()) {
- Slog.e(TAG, "error getting search session during lazy init, "
- + result.getErrorMessage());
- latch.countDown();
- return;
- }
- // TODO: Flatten callback chain with proper async framework
- final SearchSessionObservable upstream =
- new SearchSessionObservable(result.getResultValue(), latch)
- .map(this::setupSchema);
- if (observers != null) {
- for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
- : observers) {
- upstream.map(observer);
- }
- }
- upstream.map(observable -> session -> {
- mAppSearchSession = session;
- observable.next();
- });
- upstream.next();
- });
- ConcurrentUtils.waitForCountDownNoInterrupt(latch, 1500,
- "timeout accessing shortcut during lazy initialization");
- }
-
- /**
- * creates the schema for shortcut in the database
- */
- private Consumer<AppSearchSession> setupSchema(SearchSessionObservable observable) {
- return session -> {
- SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
- .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
- for (PackageIdentifier pi : mPackageIdentifiers.values()) {
- schemaBuilder = schemaBuilder
- .setSchemaTypeVisibilityForPackage(
- AppSearchPerson.SCHEMA_TYPE, true, pi)
- .setSchemaTypeVisibilityForPackage(
- AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
- }
- session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
- if (!result.isSuccess()) {
- observable.error("failed to instantiate app search schema: "
- + result.getErrorMessage());
- return;
- }
- observable.next();
- });
- };
- }
-
- /**
- * TODO: Replace this temporary implementation with proper async framework
- */
- private class SearchSessionObservable {
-
- final AppSearchSession mSession;
- final CountDownLatch mLatch;
- final ArrayList<Consumer<AppSearchSession>> mObservers = new ArrayList<>(1);
-
- SearchSessionObservable(@NonNull final AppSearchSession session,
- @NonNull final CountDownLatch latch) {
- mSession = session;
- mLatch = latch;
- }
-
- SearchSessionObservable map(
- Function<SearchSessionObservable, Consumer<AppSearchSession>> observer) {
- mObservers.add(observer.apply(this));
- return this;
- }
-
- void next() {
- if (mObservers.isEmpty()) {
- mLatch.countDown();
- return;
- }
- mObservers.remove(0).accept(mSession);
- }
-
- void error(@Nullable final String errorMessage) {
- if (errorMessage != null) {
- Slog.e(TAG, errorMessage);
- }
- mLatch.countDown();
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 38cba4c..0b21487 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -171,7 +171,7 @@
static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
@VisibleForTesting
- static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
+ static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = Integer.MAX_VALUE;
@VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@@ -2590,7 +2590,6 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.findAll(ret, query, cloneFlags);
-
return new ParceledListSlice<>(setReturnedByServer(ret));
}
@@ -5078,6 +5077,17 @@
}
@VisibleForTesting
+ void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ synchronized (mLock) {
+ final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
+ if (pkg == null) return;
+
+ pkg.mutateShortcut(shortcutId, null, cb);
+ }
+ }
+
+ @VisibleForTesting
ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
synchronized (mLock) {
final ShortcutUser user = mUsers.get(userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index ec784d0..51cb995 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
import android.content.pm.ShortcutManager;
import android.metrics.LogMaker;
@@ -35,6 +34,7 @@
import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.FgThread;
@@ -715,17 +715,26 @@
.setSubtype(totalSharingShortcutCount));
}
- void runInAppSearch(@NonNull final AppSearchManager.SearchContext searchContext,
- @NonNull final Consumer<AppSearchResult<AppSearchSession>> callback) {
+ AndroidFuture<AppSearchSession> getAppSearch(
+ @NonNull final AppSearchManager.SearchContext searchContext) {
+ final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
if (mAppSearchManager == null) {
- Slog.e(TAG, "app search manager is null");
- return;
+ future.completeExceptionally(new RuntimeException("app search manager is null"));
+ return future;
}
final long callingIdentity = Binder.clearCallingIdentity();
try {
- mAppSearchManager.createSearchSession(searchContext, mExecutor, callback);
+ mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new RuntimeException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(result.getResultValue());
+ });
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
+ return future;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fe19956..2a0257d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1507,6 +1507,26 @@
}
@Override
+ public boolean isCloneProfile(@UserIdInt int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile");
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ return userInfo != null && userInfo.isCloneProfile();
+ }
+ }
+
+ @Override
+ public boolean sharesMediaWithParent(@UserIdInt int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+ "sharesMediaWithParent");
+ synchronized (mUsersLock) {
+ UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
+ return userTypeDetails != null ? userTypeDetails.isProfile()
+ && userTypeDetails.sharesMediaWithParent() : false;
+ }
+ }
+
+ @Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
"isUserUnlockingOrUnlocked");
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 17ce386..6824f7d 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -149,6 +149,13 @@
*/
private final @Nullable int[] mDarkThemeBadgeColors;
+ /**
+ * Denotes if the user shares media with its parent user.
+ *
+ * <p> Default value is false
+ */
+ private final boolean mSharesMediaWithParent;
+
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -158,7 +165,8 @@
@Nullable Bundle defaultRestrictions,
@Nullable Bundle defaultSystemSettings,
@Nullable Bundle defaultSecureSettings,
- @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters) {
+ @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
+ boolean sharesMediaWithParent) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -177,6 +185,7 @@
this.mBadgeLabels = badgeLabels;
this.mBadgeColors = badgeColors;
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
+ this.mSharesMediaWithParent = sharesMediaWithParent;
}
/**
@@ -291,6 +300,13 @@
return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
}
+ /**
+ * Returns true if the user has shared media with parent user or false otherwise.
+ */
+ public boolean sharesMediaWithParent() {
+ return mSharesMediaWithParent;
+ }
+
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -318,7 +334,6 @@
: Collections.emptyList();
}
-
/** Dumps details of the UserTypeDetails. Do not parse this. */
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mName: "); pw.println(mName);
@@ -383,6 +398,7 @@
private @DrawableRes int mIconBadge = Resources.ID_NULL;
private @DrawableRes int mBadgePlain = Resources.ID_NULL;
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
+ private boolean mSharesMediaWithParent = false;
public Builder setName(String name) {
mName = name;
@@ -473,6 +489,15 @@
return this;
}
+ /**
+ * Sets shared media property for the user.
+ * @param sharesMediaWithParent the value to be set, true or false
+ */
+ public Builder setSharesMediaWithParent(boolean sharesMediaWithParent) {
+ mSharesMediaWithParent = sharesMediaWithParent;
+ return this;
+ }
+
@UserInfoFlag int getBaseType() {
return mBaseType;
}
@@ -502,7 +527,7 @@
mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors,
mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors,
mDefaultRestrictions, mDefaultSystemSettings, mDefaultSecureSettings,
- mDefaultCrossProfileIntentFilters);
+ mDefaultCrossProfileIntentFilters, mSharesMediaWithParent);
}
private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 6aac0b2..e8421a5 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -29,6 +29,7 @@
import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
@@ -100,6 +101,7 @@
builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
+ builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
if (Build.IS_DEBUGGABLE) {
builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
}
@@ -108,6 +110,21 @@
}
/**
+ * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE}
+ * configuration.
+ */
+ // TODO(b/182396009): Add default restrictions, if needed for clone user type.
+ private static UserTypeDetails.Builder getDefaultTypeProfileClone() {
+ return new UserTypeDetails.Builder()
+ .setName(USER_TYPE_PROFILE_CLONE)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowedPerParent(1)
+ .setLabel(0)
+ .setDefaultRestrictions(null)
+ .setSharesMediaWithParent(true);
+ }
+
+ /**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED}
* configuration.
*/
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 27bf8a13..f0d54b4 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -206,6 +206,12 @@
STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
}
+ private static final Set<String> NEARBY_DEVICES_PERMISSIONS = new ArraySet<>();
+ static {
+ NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
+ NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
+ }
+
private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
private static final String ACTION_TRACK = "com.android.fitness.TRACK";
@@ -733,14 +739,15 @@
PHONE_PERMISSIONS, SMS_PERMISSIONS, CAMERA_PERMISSIONS,
SENSORS_PERMISSIONS, STORAGE_PERMISSIONS);
grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId,
- ALWAYS_LOCATION_PERMISSIONS, ACTIVITY_RECOGNITION_PERMISSIONS);
+ ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS,
+ ACTIVITY_RECOGNITION_PERMISSIONS);
}
}
if (locationExtraPackageNames != null) {
// Also grant location and activity recognition permission to location extra packages.
for (String packageName : locationExtraPackageNames) {
grantPermissionsToSystemPackage(pm, packageName, userId,
- ALWAYS_LOCATION_PERMISSIONS);
+ ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS);
grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId,
ACTIVITY_RECOGNITION_PERMISSIONS);
}
@@ -809,7 +816,7 @@
// Companion devices
grantSystemFixedPermissionsToSystemPackage(pm,
CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, userId,
- ALWAYS_LOCATION_PERMISSIONS);
+ ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS);
// Ringtone Picker
grantSystemFixedPermissionsToSystemPackage(pm,
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index a0e252a..8075bdb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -284,7 +284,8 @@
int state) throws NameNotFoundException {
if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
if (state != DomainVerificationState.STATE_SUCCESS) {
- return DomainVerificationManager.ERROR_INVALID_STATE_CODE;
+ throw new IllegalArgumentException(
+ "Caller is not allowed to set state code " + state);
}
}
@@ -1119,7 +1120,7 @@
@NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
@Nullable Integer userIdForFilter) throws NameNotFoundException {
if (domainSetId == null) {
- return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL);
+ throw new IllegalArgumentException("domainSetId cannot be null");
}
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId);
@@ -1140,9 +1141,9 @@
}
if (CollectionUtils.isEmpty(domains)) {
- return GetAttachedResult.error(
- DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY);
+ throw new IllegalArgumentException("Provided domain set cannot be empty");
}
+
AndroidPackage pkg = pkgSetting.getPkg();
ArraySet<String> declaredDomains = forAutoVerify
? mCollector.collectValidAutoVerifyDomains(pkg)
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index beebb31..fe21201 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -21,11 +21,13 @@
import android.annotation.IntDef;
import android.content.Context;
import android.content.IntentSender;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.boot.V1_0.IBootControl;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Binder;
+import android.os.Environment;
import android.os.IRecoverySystem;
import android.os.IRecoverySystemProgressListener;
import android.os.PowerManager;
@@ -52,6 +54,7 @@
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.FileWriter;
import java.io.IOException;
@@ -87,6 +90,12 @@
private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
+ static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp";
+ static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count";
+
+ static final String LSKF_CAPTURED_TIMESTAMP_PREF = "lskf_captured_timestamp";
+ static final String LSKF_CAPTURED_COUNT_PREF = "lskf_captured_count";
+
private final Injector mInjector;
private final Context mContext;
@@ -127,7 +136,7 @@
*/
@IntDef({ ROR_NEED_PREPARATION,
ROR_SKIP_PREPARATION_AND_NOTIFY,
- ROR_SKIP_PREPARATION_NOT_NOTIFY })
+ ROR_SKIP_PREPARATION_NOT_NOTIFY})
private @interface ResumeOnRebootActionsOnRequest {}
/**
@@ -139,7 +148,7 @@
private @interface ResumeOnRebootActionsOnClear {}
/**
- * The error code for reboots initiated by resume on reboot clients.
+ * The error codes for reboots initiated by resume on reboot clients.
*/
private static final int REBOOT_ERROR_NONE = 0;
private static final int REBOOT_ERROR_UNKNOWN = 1;
@@ -156,11 +165,64 @@
REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE})
private @interface ResumeOnRebootRebootErrorCode {}
+ /**
+ * Manages shared preference, i.e. the storage used for metrics reporting.
+ */
+ public static class PreferencesManager {
+ private static final String METRICS_DIR = "recovery_system";
+ private static final String METRICS_PREFS_FILE = "RecoverySystemMetricsPrefs.xml";
+
+ protected final SharedPreferences mSharedPreferences;
+ private final File mMetricsPrefsFile;
+
+ PreferencesManager(Context context) {
+ File prefsDir = new File(Environment.getDataSystemCeDirectory(USER_SYSTEM),
+ METRICS_DIR);
+ mMetricsPrefsFile = new File(prefsDir, METRICS_PREFS_FILE);
+ mSharedPreferences = context.getSharedPreferences(mMetricsPrefsFile, 0);
+ }
+
+ /** Reads the value of a given key with type long. **/
+ public long getLong(String key, long defaultValue) {
+ return mSharedPreferences.getLong(key, defaultValue);
+ }
+
+ /** Reads the value of a given key with type int. **/
+ public int getInt(String key, int defaultValue) {
+ return mSharedPreferences.getInt(key, defaultValue);
+ }
+
+ /** Stores the value of a given key with type long. **/
+ public void putLong(String key, long value) {
+ mSharedPreferences.edit().putLong(key, value).commit();
+ }
+
+ /** Stores the value of a given key with type int. **/
+ public void putInt(String key, int value) {
+ mSharedPreferences.edit().putInt(key, value).commit();
+ }
+
+ /** Increments the value of a given key with type int. **/
+ public synchronized void incrementIntKey(String key, int defaultInitialValue) {
+ int oldValue = getInt(key, defaultInitialValue);
+ putInt(key, oldValue + 1);
+ }
+
+ /** Delete the preference file and cleanup all metrics storage. **/
+ public void deletePrefsFile() {
+ if (!mMetricsPrefsFile.delete()) {
+ Slog.w(TAG, "Failed to delete metrics prefs");
+ }
+ }
+ }
+
static class Injector {
protected final Context mContext;
+ protected final PreferencesManager mPrefs;
Injector(Context context) {
mContext = context;
+ mPrefs = new PreferencesManager(context);
}
public Context getContext() {
@@ -236,6 +298,14 @@
return -1;
}
+ public PreferencesManager getMetricsPrefs() {
+ return mPrefs;
+ }
+
+ public long getCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
public void reportRebootEscrowPreparationMetrics(int uid,
@ResumeOnRebootActionsOnRequest int requestResult, int requestedClientCount) {
FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_PREPARATION_REPORTED, uid,
@@ -414,7 +484,7 @@
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY)
!= PackageManager.PERMISSION_GRANTED
&& mContext.checkCallingOrSelfPermission(android.Manifest.permission.REBOOT)
- != PackageManager.PERMISSION_GRANTED) {
+ != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller must have " + android.Manifest.permission.RECOVERY
+ " or " + android.Manifest.permission.REBOOT + " for resume on reboot.");
}
@@ -427,6 +497,12 @@
pendingRequestCount = mCallerPendingRequest.size();
}
+ // Save the timestamp and request count for new ror request
+ PreferencesManager prefs = mInjector.getMetricsPrefs();
+ prefs.putLong(packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX,
+ mInjector.getCurrentTimeMillis());
+ prefs.incrementIntKey(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, 0);
+
mInjector.reportRebootEscrowPreparationMetrics(uid, requestResult, pendingRequestCount);
}
@@ -486,15 +562,31 @@
}
private void reportMetricsOnPreparedForReboot() {
+ long currentTimestamp = mInjector.getCurrentTimeMillis();
+
List<String> preparedClients;
synchronized (this) {
preparedClients = new ArrayList<>(mCallerPreparedForReboot);
}
+ // Save the timestamp & lskf capture count for lskf capture
+ PreferencesManager prefs = mInjector.getMetricsPrefs();
+ prefs.putLong(LSKF_CAPTURED_TIMESTAMP_PREF, currentTimestamp);
+ prefs.incrementIntKey(LSKF_CAPTURED_COUNT_PREF, 0);
+
for (String packageName : preparedClients) {
int uid = mInjector.getUidFromPackageName(packageName);
+
+ int durationSeconds = -1;
+ long requestLskfTimestamp = prefs.getLong(
+ packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX, -1);
+ if (requestLskfTimestamp != -1 && currentTimestamp > requestLskfTimestamp) {
+ durationSeconds = (int) (currentTimestamp - requestLskfTimestamp) / 1000;
+ }
+ Slog.i(TAG, String.format("Reporting lskf captured, lskf capture takes %d seconds for"
+ + " package %s", durationSeconds, packageName));
mInjector.reportRebootEscrowLskfCapturedMetrics(uid, preparedClients.size(),
- -1 /* duration */);
+ durationSeconds);
}
}
@@ -541,6 +633,7 @@
Slog.w(TAG, "Missing packageName when clearing lskf.");
return false;
}
+ // TODO(179105110) Clear the RoR metrics for the given packageName.
@ResumeOnRebootActionsOnClear int action = updateRoRPreparationStateOnClear(packageName);
switch (action) {
@@ -641,7 +734,15 @@
return REBOOT_ERROR_SLOT_MISMATCH;
}
- if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+ final long origId = Binder.clearCallingIdentity();
+ boolean result;
+ try {
+ result = mInjector.getLockSettingsService().armRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ if (!result) {
Slog.w(TAG, "Failure to escrow key for reboot");
return REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE;
}
@@ -649,20 +750,42 @@
return REBOOT_ERROR_NONE;
}
+ private boolean useServerBasedRoR() {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
+ "server_based_ror_enabled", false);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
private void reportMetricsOnRebootWithLskf(String packageName, boolean slotSwitch,
@ResumeOnRebootRebootErrorCode int errorCode) {
int uid = mInjector.getUidFromPackageName(packageName);
- boolean serverBased = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
- "server_based_ror_enabled", false);
+ boolean serverBased = useServerBasedRoR();
int preparedClientCount;
synchronized (this) {
preparedClientCount = mCallerPreparedForReboot.size();
}
- // TODO(b/179105110) report the true value of duration and counts
+ long currentTimestamp = mInjector.getCurrentTimeMillis();
+ int durationSeconds = -1;
+ PreferencesManager prefs = mInjector.getMetricsPrefs();
+ long lskfCapturedTimestamp = prefs.getLong(LSKF_CAPTURED_TIMESTAMP_PREF, -1);
+ if (lskfCapturedTimestamp != -1 && currentTimestamp > lskfCapturedTimestamp) {
+ durationSeconds = (int) (currentTimestamp - lskfCapturedTimestamp) / 1000;
+ }
+
+ int requestCount = prefs.getInt(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, -1);
+ int lskfCapturedCount = prefs.getInt(LSKF_CAPTURED_COUNT_PREF, -1);
+
+ Slog.i(TAG, String.format("Reporting reboot with lskf, package name %s, client count %d,"
+ + " request count %d, lskf captured count %d, duration since lskf captured"
+ + " %d seconds.", packageName, preparedClientCount, requestCount,
+ lskfCapturedCount, durationSeconds));
mInjector.reportRebootEscrowRebootMetrics(errorCode, uid, preparedClientCount,
- 1 /* request count */, slotSwitch, serverBased,
- -1 /* duration */, 1 /* lskf capture count */);
+ requestCount, slotSwitch, serverBased, durationSeconds, lskfCapturedCount);
}
private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) {
@@ -673,6 +796,9 @@
return false;
}
+ // Clear the metrics prefs after a successful RoR reboot.
+ mInjector.getMetricsPrefs().deletePrefsFile();
+
PowerManager pm = mInjector.getPowerManager();
pm.reboot(reason);
return true;
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
new file mode 100644
index 0000000..3ca8a5a
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2021 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.servicewatcher;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import android.annotation.BoolRes;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Supplies services based on the current active user and version as defined in the service
+ * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
+ * ensure only system (ie, privileged) services are matched. It also handles services that are not
+ * direct boot aware, and will automatically pick the best service as the user's direct boot state
+ * changes.
+ *
+ * <p>Optionally, two permissions may be specified: (1) a caller permission - any service that does
+ * not require callers to hold this permission is rejected (2) a service permission - any service
+ * whose package does not hold this permission is rejected.
+ */
+public class CurrentUserServiceSupplier extends BroadcastReceiver implements
+ ServiceSupplier<CurrentUserServiceSupplier.BoundServiceInfo> {
+
+ private static final String TAG = "CurrentUserServiceSupplier";
+
+ private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+ private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+
+ private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
+ if (o1 == o2) {
+ return 0;
+ } else if (o1 == null) {
+ return -1;
+ } else if (o2 == null) {
+ return 1;
+ }
+
+ // ServiceInfos with higher version numbers always win. if version numbers are equal
+ // then we prefer components that work for all users vs components that only work for a
+ // single user at a time. otherwise everything's equal.
+ int ret = Integer.compare(o1.getVersion(), o2.getVersion());
+ if (ret == 0) {
+ if (o1.getUserId() != USER_SYSTEM && o2.getUserId() == USER_SYSTEM) {
+ ret = -1;
+ } else if (o1.getUserId() == USER_SYSTEM && o2.getUserId() != USER_SYSTEM) {
+ ret = 1;
+ }
+ }
+ return ret;
+ };
+
+ /** Bound service information with version information. */
+ public static class BoundServiceInfo extends ServiceWatcher.BoundServiceInfo {
+
+ private static int parseUid(ResolveInfo resolveInfo) {
+ int uid = resolveInfo.serviceInfo.applicationInfo.uid;
+ Bundle metadata = resolveInfo.serviceInfo.metaData;
+ if (metadata != null && metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false)) {
+ // reconstruct a uid for the same app but with the system user - hope this exists
+ uid = UserHandle.getUid(USER_SYSTEM, UserHandle.getAppId(uid));
+ }
+ return uid;
+ }
+
+ private static int parseVersion(ResolveInfo resolveInfo) {
+ int version = Integer.MIN_VALUE;
+ if (resolveInfo.serviceInfo.metaData != null) {
+ version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
+ }
+ return version;
+ }
+
+ private final int mVersion;
+ private final @Nullable Bundle mMetadata;
+
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(action, parseUid(resolveInfo), resolveInfo.serviceInfo.getComponentName(),
+ parseVersion(resolveInfo), resolveInfo.serviceInfo.metaData);
+ }
+
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName, int version,
+ @Nullable Bundle metadata) {
+ super(action, uid, componentName);
+
+ mVersion = version;
+ mMetadata = metadata;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public @Nullable Bundle getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "@" + mVersion;
+ }
+ }
+
+ private static @Nullable String retrieveExplicitPackage(Context context,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+ Resources resources = context.getResources();
+ boolean enableOverlay = resources.getBoolean(enableOverlayResId);
+ if (!enableOverlay) {
+ return resources.getString(nonOverlayPackageResId);
+ } else {
+ return null;
+ }
+ }
+
+ private final Context mContext;
+ private final ActivityManagerInternal mActivityManager;
+ private final Intent mIntent;
+ // a permission that the service forces callers (ie ServiceWatcher/system server) to hold
+ private final @Nullable String mCallerPermission;
+ // a permission that the service package should hold
+ private final @Nullable String mServicePermission;
+
+ private volatile ServiceChangedListener mListener;
+
+ public CurrentUserServiceSupplier(Context context, String action) {
+ this(context, action, null, null, null);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+ this(context, action,
+ retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId), null,
+ null);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
+ @Nullable String callerPermission, @Nullable String servicePermission) {
+ this(context, action,
+ retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId),
+ callerPermission, servicePermission);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @Nullable String explicitPackage, @Nullable String callerPermission,
+ @Nullable String servicePermission) {
+ mContext = context;
+ mActivityManager = Objects.requireNonNull(
+ LocalServices.getService(ActivityManagerInternal.class));
+ mIntent = new Intent(action);
+
+ if (explicitPackage != null) {
+ mIntent.setPackage(explicitPackage);
+ }
+
+ mCallerPermission = callerPermission;
+ mServicePermission = servicePermission;
+ }
+
+ @Override
+ public boolean hasMatchingService() {
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentServicesAsUser(mIntent,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
+ UserHandle.USER_SYSTEM);
+ return !resolveInfos.isEmpty();
+ }
+
+ @Override
+ public void register(ServiceChangedListener listener) {
+ Preconditions.checkState(mListener == null);
+
+ mListener = listener;
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiverAsUser(this, UserHandle.ALL, intentFilter, null,
+ FgThread.getHandler());
+ }
+
+ @Override
+ public void unregister() {
+ Preconditions.checkArgument(mListener != null);
+
+ mListener = null;
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public BoundServiceInfo getServiceInfo() {
+ BoundServiceInfo bestServiceInfo = null;
+
+ // only allow privileged services in the correct direct boot state to match
+ int currentUserId = mActivityManager.getCurrentUserId();
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
+ mIntent,
+ GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
+ currentUserId);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo service = Objects.requireNonNull(resolveInfo.serviceInfo);
+
+ if (mCallerPermission != null) {
+ if (!mCallerPermission.equals(service.permission)) {
+ Log.d(TAG, service.getComponentName().flattenToShortString()
+ + " disqualified due to not requiring " + mCallerPermission);
+ continue;
+ }
+ }
+
+ BoundServiceInfo serviceInfo = new BoundServiceInfo(mIntent.getAction(), resolveInfo);
+
+ if (mServicePermission != null) {
+ if (PermissionManager.checkPackageNamePermission(mServicePermission,
+ service.packageName, serviceInfo.getUserId()) != PERMISSION_GRANTED) {
+ Log.d(TAG, serviceInfo.getComponentName().flattenToShortString()
+ + " disqualified due to not holding " + mCallerPermission);
+ continue;
+ }
+ }
+
+ if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
+ bestServiceInfo = serviceInfo;
+ }
+ }
+
+ return bestServiceInfo;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId == UserHandle.USER_NULL) {
+ return;
+ }
+ ServiceChangedListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ switch (action) {
+ case Intent.ACTION_USER_SWITCHED:
+ listener.onServiceChanged();
+ break;
+ case Intent.ACTION_USER_UNLOCKED:
+ // user unlocked implies direct boot mode may have changed
+ if (userId == mActivityManager.getCurrentUserId()) {
+ listener.onServiceChanged();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 5d49663..030bbd2 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,516 +16,244 @@
package com.android.server.servicewatcher;
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-import static android.content.Context.BIND_NOT_VISIBLE;
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-import com.android.internal.annotations.Immutable;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.function.Predicate;
/**
- * Maintains a binding to the best service that matches the given intent information. Bind and
- * unbind callbacks, as well as all binder operations, will all be run on a single thread.
+ * A ServiceWatcher is responsible for continuously maintaining an active binding to a service
+ * selected by it's {@link ServiceSupplier}. The {@link ServiceSupplier} may change the service it
+ * selects over time, and the currently bound service may crash, restart, have a user change, have
+ * changes made to its package, and so on and so forth. The ServiceWatcher is responsible for
+ * maintaining the binding across all these changes.
+ *
+ * <p>Clients may invoke {@link BinderOperation}s on the ServiceWatcher, and it will make a best
+ * effort to run these on the currently bound service, but individual operations may fail (if there
+ * is no service currently bound for instance). In order to help clients maintain the correct state,
+ * clients may supply a {@link ServiceListener}, which is informed when the ServiceWatcher connects
+ * and disconnects from a service. This allows clients to bring a bound service back into a known
+ * state on connection, and then run binder operations from there. In order to help clients
+ * accomplish this, ServiceWatcher guarantees that {@link BinderOperation}s and the
+ * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
+ * can be established between them.
+ *
+ * There is never any guarantee of whether a ServiceWatcher is currently connected to a service, and
+ * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
+ * on this, and instead use {@link ServiceListener} notifications as necessary to recover from
+ * failures.
*/
-public class ServiceWatcher implements ServiceConnection {
+public interface ServiceWatcher {
- private static final String TAG = "ServiceWatcher";
- private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
- private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
-
- private static final long RETRY_DELAY_MS = 15 * 1000;
-
- private static final Predicate<ResolveInfo> DEFAULT_SERVICE_CHECK_PREDICATE = x -> true;
-
- /** Function to run on binder interface. */
- public interface BinderRunner {
- /** Called to run client code with the binder. */
+ /**
+ * Operation to run on a binder interface. All operations will be run on the thread used by the
+ * ServiceWatcher this is run with.
+ */
+ interface BinderOperation {
+ /** Invoked to run the operation. Run on the ServiceWatcher thread. */
void run(IBinder binder) throws RemoteException;
+
/**
- * Called if an error occurred and the function could not be run. This callback is only
- * intended for resource deallocation and cleanup in response to a single binder operation,
- * it should not be used to propagate errors further.
+ * Invoked if {@link #run(IBinder)} could not be invoked because there was no current
+ * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
+ * {@link RuntimeException}). This callback is only intended for resource deallocation and
+ * cleanup in response to a single binder operation, it should not be used to propagate
+ * errors further. Run on the ServiceWatcher thread.
*/
default void onError() {}
}
- /** Function to run on binder interface when first bound. */
- public interface OnBindRunner {
- /** Called to run client code with the binder. */
- void run(IBinder binder, BoundService service) throws RemoteException;
+ /**
+ * Listener for bind and unbind events. All operations will be run on the thread used by the
+ * ServiceWatcher this is run with.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
+ /** Invoked when a service is bound. Run on the ServiceWatcher thread. */
+ void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
+
+ /** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
+ void onUnbind();
}
/**
- * Information on the service ServiceWatcher has selected as the best option for binding.
+ * A listener for when a {@link ServiceSupplier} decides that the current service has changed.
*/
- @Immutable
- public static final class BoundService implements Comparable<BoundService> {
+ interface ServiceChangedListener {
+ /**
+ * Should be invoked when the current service may have changed.
+ */
+ void onServiceChanged();
+ }
- public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null,
- false, null, -1);
+ /**
+ * This supplier encapsulates the logic of deciding what service a {@link ServiceWatcher} should
+ * be bound to at any given moment.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceSupplier<TBoundServiceInfo extends BoundServiceInfo> {
+ /**
+ * Should return true if there exists at least one service capable of meeting the criteria
+ * of this supplier. This does not imply that {@link #getServiceInfo()} will always return a
+ * non-null result, as any service may be disqualified for various reasons at any point in
+ * time. May be invoked at any time from any thread and thus should generally not have any
+ * dependency on the other methods in this interface.
+ */
+ boolean hasMatchingService();
- public final int version;
- @Nullable
- public final ComponentName component;
- public final boolean serviceIsMultiuser;
- public final int uid;
- @Nullable
- public final Bundle metadata;
+ /**
+ * Invoked when the supplier should start monitoring for any changes that could result in a
+ * different service selection, and should invoke
+ * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
+ * may be invoked after this method is called.
+ */
+ void register(ServiceChangedListener listener);
- BoundService(ResolveInfo resolveInfo) {
- Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null);
+ /**
+ * Invoked when the supplier should stop monitoring for any changes that could result in a
+ * different service selection, should no longer invoke
+ * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
+ * invoked after this method is called.
+ */
+ void unregister();
- metadata = resolveInfo.serviceInfo.metaData;
- if (metadata != null) {
- version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
- serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
- } else {
- version = Integer.MIN_VALUE;
- serviceIsMultiuser = false;
- }
+ /**
+ * Must be implemented to return the current service selected by this supplier. May return
+ * null if no service currently meets the criteria. Only invoked while registered.
+ */
+ @Nullable TBoundServiceInfo getServiceInfo();
+ }
- component = resolveInfo.serviceInfo.getComponentName();
- uid = resolveInfo.serviceInfo.applicationInfo.uid;
+ /**
+ * Information on the service selected as the best option for binding.
+ */
+ class BoundServiceInfo {
+
+ protected final @Nullable String mAction;
+ protected final int mUid;
+ protected final ComponentName mComponentName;
+
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(action, resolveInfo.serviceInfo.applicationInfo.uid,
+ resolveInfo.serviceInfo.getComponentName());
}
- private BoundService(int version, @Nullable ComponentName component,
- boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) {
- Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE);
- this.version = version;
- this.component = component;
- this.serviceIsMultiuser = serviceIsMultiuser;
- this.metadata = metadata;
- this.uid = uid;
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
+ mAction = action;
+ mUid = uid;
+ mComponentName = Objects.requireNonNull(componentName);
}
- public @Nullable String getPackageName() {
- return component != null ? component.getPackageName() : null;
+ /** Returns the action associated with this bound service. */
+ public @Nullable String getAction() {
+ return mAction;
+ }
+
+ /** Returns the component of this bound service. */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /** Returns the user id for this bound service. */
+ public @UserIdInt int getUserId() {
+ return UserHandle.getUserId(mUid);
}
@Override
- public boolean equals(Object o) {
+ public final boolean equals(Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof BoundService)) {
+ if (!(o instanceof BoundServiceInfo)) {
return false;
}
- BoundService that = (BoundService) o;
- return version == that.version && uid == that.uid
- && Objects.equals(component, that.component);
+
+ BoundServiceInfo that = (BoundServiceInfo) o;
+ return mUid == that.mUid
+ && Objects.equals(mAction, that.mAction)
+ && mComponentName.equals(that.mComponentName);
}
@Override
- public int hashCode() {
- return Objects.hash(version, component, uid);
- }
-
- @Override
- public int compareTo(BoundService that) {
- // ServiceInfos with higher version numbers always win (having a version number >
- // MIN_VALUE implies having a non-null component). if version numbers are equal, a
- // non-null component wins over a null component. if the version numbers are equal and
- // both components exist then we prefer components that work for all users vs components
- // that only work for a single user at a time. otherwise everything's equal.
- int ret = Integer.compare(version, that.version);
- if (ret == 0) {
- if (component == null && that.component != null) {
- ret = -1;
- } else if (component != null && that.component == null) {
- ret = 1;
- } else {
- if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM
- && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) {
- ret = -1;
- } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM
- && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) {
- ret = 1;
- }
- }
- }
- return ret;
+ public final int hashCode() {
+ return Objects.hash(mAction, mUid, mComponentName);
}
@Override
public String toString() {
- if (component == null) {
+ if (mComponentName == null) {
return "none";
} else {
- return component.toShortString() + "@" + version + "[u"
- + UserHandle.getUserId(uid) + "]";
+ return mUid + "/" + mComponentName.flattenToShortString();
}
}
}
- private final Context mContext;
- private final Handler mHandler;
- private final Intent mIntent;
- private final Predicate<ResolveInfo> mServiceCheckPredicate;
-
- private final PackageMonitor mPackageMonitor = new PackageMonitor() {
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- return true;
- }
-
- @Override
- public void onSomePackagesChanged() {
- onBestServiceChanged(false);
- }
- };
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action == null) {
- return;
- }
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId == UserHandle.USER_NULL) {
- return;
- }
-
- switch (action) {
- case Intent.ACTION_USER_SWITCHED:
- onUserSwitched(userId);
- break;
- case Intent.ACTION_USER_UNLOCKED:
- onUserUnlocked(userId);
- break;
- default:
- break;
- }
-
- }
- };
-
- // read/write from handler thread only
- private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>();
-
- @Nullable
- private final OnBindRunner mOnBind;
-
- @Nullable
- private final Runnable mOnUnbind;
-
- // read/write from handler thread only
- private boolean mRegistered;
-
- // read/write from handler thread only
- private int mCurrentUserId;
-
- // write from handler thread only, read anywhere
- private volatile BoundService mTargetService;
- private volatile IBinder mBinder;
-
- public ServiceWatcher(Context context, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- this(context, FgThread.getHandler(), action, onBind, onUnbind, enableOverlayResId,
- nonOverlayPackageResId, DEFAULT_SERVICE_CHECK_PREDICATE);
- }
-
- public ServiceWatcher(Context context, Handler handler, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- this(context, handler, action, onBind, onUnbind, enableOverlayResId, nonOverlayPackageResId,
- DEFAULT_SERVICE_CHECK_PREDICATE);
- }
-
- public ServiceWatcher(Context context, Handler handler, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
- @NonNull Predicate<ResolveInfo> serviceCheckPredicate) {
- mContext = context;
- mHandler = handler;
- mIntent = new Intent(Objects.requireNonNull(action));
- mServiceCheckPredicate = Objects.requireNonNull(serviceCheckPredicate);
-
- Resources resources = context.getResources();
- boolean enableOverlay = resources.getBoolean(enableOverlayResId);
- if (!enableOverlay) {
- mIntent.setPackage(resources.getString(nonOverlayPackageResId));
- }
-
- mOnBind = onBind;
- mOnUnbind = onUnbind;
-
- mCurrentUserId = UserHandle.USER_NULL;
-
- mTargetService = BoundService.NONE;
- mBinder = null;
- }
-
- /**
- * Returns true if there is at least one component that could satisfy the ServiceWatcher's
- * constraints.
- */
- public boolean checkServiceResolves() {
- List<ResolveInfo> resolveInfos = mContext.getPackageManager()
- .queryIntentServicesAsUser(mIntent,
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
- UserHandle.USER_SYSTEM);
- for (ResolveInfo resolveInfo : resolveInfos) {
- if (mServiceCheckPredicate.test(resolveInfo)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Starts the process of determining the best matching service and maintaining a binding to it.
- */
- public void register() {
- mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal,
- ServiceWatcher.this));
- }
-
- private void registerInternal() {
- Preconditions.checkState(!mRegistered);
-
- mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
- intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null,
- mHandler);
-
- // TODO: This makes the behavior of the class unpredictable as the caller needs
- // to know the internal impl detail that calling register would pick the current user.
- mCurrentUserId = ActivityManager.getCurrentUser();
-
- mRegistered = true;
-
- mHandler.post(() -> onBestServiceChanged(false));
- }
-
- /**
- * Stops the process of determining the best matching service and releases any binding.
- */
- public void unregister() {
- mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal,
- ServiceWatcher.this));
- }
-
- private void unregisterInternal() {
- Preconditions.checkState(mRegistered);
-
- mRegistered = false;
-
- mPackageMonitor.unregister();
- mContext.unregisterReceiver(mBroadcastReceiver);
-
- mHandler.post(() -> onBestServiceChanged(false));
- }
-
- private void onBestServiceChanged(boolean forceRebind) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- BoundService bestServiceInfo = BoundService.NONE;
-
- if (mRegistered) {
- List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
- mIntent,
- GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
- mCurrentUserId);
- for (ResolveInfo resolveInfo : resolveInfos) {
- if (!mServiceCheckPredicate.test(resolveInfo)) {
- continue;
- }
- BoundService serviceInfo = new BoundService(resolveInfo);
- if (serviceInfo.compareTo(bestServiceInfo) > 0) {
- bestServiceInfo = serviceInfo;
- }
- }
- }
-
- if (forceRebind || !bestServiceInfo.equals(mTargetService)) {
- rebind(bestServiceInfo);
- }
- }
-
- private void rebind(BoundService newServiceInfo) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (!mTargetService.equals(BoundService.NONE)) {
- if (D) {
- Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
- }
-
- mContext.unbindService(this);
- onServiceDisconnected(mTargetService.component);
- mPendingBinds.remove(mTargetService.component);
- mTargetService = BoundService.NONE;
- }
-
- mTargetService = newServiceInfo;
- if (mTargetService.equals(BoundService.NONE)) {
- return;
- }
-
- Preconditions.checkState(mTargetService.component != null);
-
- Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
-
- Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
- if (!mContext.bindServiceAsUser(bindIntent, this,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
- mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) {
- mTargetService = BoundService.NONE;
- Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later");
- mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS);
- } else {
- mPendingBinds.put(mTargetService.component, mTargetService);
- }
- }
-
- @Override
- public final void onServiceConnected(ComponentName component, IBinder binder) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- Preconditions.checkState(mBinder == null);
-
- if (D) {
- Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
- }
-
- final BoundService boundService = mPendingBinds.remove(component);
- if (boundService == null) {
- return;
- }
-
- mBinder = binder;
- if (mOnBind != null) {
- try {
- mOnBind.run(binder, boundService);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, getLogPrefix() + " exception running on " + component, e);
- }
- }
- }
-
- @Override
- public final void onServiceDisconnected(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (mBinder == null) {
- return;
- }
-
- if (D) {
- Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
- }
-
- mBinder = null;
- if (mOnUnbind != null) {
- mOnUnbind.run();
- }
- }
-
- @Override
- public final void onBindingDied(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
-
- onBestServiceChanged(true);
- }
-
- @Override
- public final void onNullBinding(ComponentName component) {
- Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
- }
-
- void onUserSwitched(@UserIdInt int userId) {
- mCurrentUserId = userId;
- onBestServiceChanged(false);
- }
-
- void onUserUnlocked(@UserIdInt int userId) {
- if (userId == mCurrentUserId) {
- onBestServiceChanged(false);
- }
- }
-
/**
- * Runs the given function asynchronously if and only if currently connected. Suppresses any
- * RemoteException thrown during execution.
+ * Creates a new ServiceWatcher instance.
*/
- public final void runOnBinder(BinderRunner runner) {
- mHandler.post(() -> {
- if (mBinder == null) {
- runner.onError();
- return;
- }
-
- try {
- runner.run(mBinder);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, getLogPrefix() + " exception running on " + mTargetService, e);
- runner.onError();
- }
- });
- }
-
- private String getLogPrefix() {
- return "[" + mIntent.getAction() + "]";
- }
-
- @Override
- public String toString() {
- return mTargetService.toString();
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+ Context context,
+ String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return create(context, FgThread.getHandler(), tag, serviceSupplier, serviceListener);
}
/**
- * Dump for debugging.
+ * Creates a new ServiceWatcher instance that runs on the given handler.
*/
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("target service=" + mTargetService);
- pw.println("connected=" + (mBinder != null));
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+ Context context,
+ Handler handler,
+ String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return new ServiceWatcherImpl<>(context, handler, tag, serviceSupplier, serviceListener);
}
-}
+
+ /**
+ * Returns true if there is at least one service that the ServiceWatcher could hypothetically
+ * bind to, as selected by the {@link ServiceSupplier}.
+ */
+ boolean checkServiceResolves();
+
+ /**
+ * Registers the ServiceWatcher, so that it will begin maintaining an active binding to the
+ * service selected by {@link ServiceSupplier}, until {@link #unregister()} is called.
+ */
+ void register();
+
+ /**
+ * Unregisters the ServiceWatcher, so that it will release any active bindings. If the
+ * ServiceWatcher is currently bound, this will result in one final
+ * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
+ * (but which is guaranteed to occur before any further
+ * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
+ * call to {@link #register()}).
+ */
+ void unregister();
+
+ /**
+ * Runs the given binder operation on the currently bound service (if available). The operation
+ * will always fail if the ServiceWatcher is not currently registered.
+ */
+ void runOnBinder(BinderOperation operation);
+
+ /**
+ * Dumps ServiceWatcher information.
+ */
+ void dump(PrintWriter pw);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
new file mode 100644
index 0000000..e718ba3
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2021 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.servicewatcher;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.content.Context.BIND_NOT_VISIBLE;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.Preconditions;
+import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows
+ * us to store the generic relationship between the service supplier and the service listener, while
+ * hiding the generics from clients, simplifying the API.
+ */
+class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher,
+ ServiceChangedListener {
+
+ static final String TAG = "ServiceWatcher";
+ static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final long RETRY_DELAY_MS = 15 * 1000;
+
+ final Context mContext;
+ final Handler mHandler;
+ final String mTag;
+ final ServiceSupplier<TBoundServiceInfo> mServiceSupplier;
+ final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
+
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ return true;
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ onServiceChanged(false);
+ }
+ };
+
+ @GuardedBy("this")
+ private boolean mRegistered = false;
+ @GuardedBy("this")
+ private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
+
+ ServiceWatcherImpl(Context context, Handler handler, String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ mContext = context;
+ mHandler = handler;
+ mTag = tag;
+ mServiceSupplier = serviceSupplier;
+ mServiceListener = serviceListener;
+ }
+
+ @Override
+ public boolean checkServiceResolves() {
+ return mServiceSupplier.hasMatchingService();
+ }
+
+ @Override
+ public synchronized void register() {
+ Preconditions.checkState(!mRegistered);
+
+ mRegistered = true;
+ mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler);
+ mServiceSupplier.register(this);
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void unregister() {
+ Preconditions.checkState(mRegistered);
+
+ mServiceSupplier.unregister();
+ mPackageMonitor.unregister();
+ mRegistered = false;
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void onServiceChanged() {
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void runOnBinder(BinderOperation operation) {
+ MyServiceConnection serviceConnection = mServiceConnection;
+ mHandler.post(() -> serviceConnection.runOnBinder(operation));
+ }
+
+ synchronized void onServiceChanged(boolean forceRebind) {
+ TBoundServiceInfo newBoundServiceInfo;
+ if (mRegistered) {
+ newBoundServiceInfo = mServiceSupplier.getServiceInfo();
+ } else {
+ newBoundServiceInfo = null;
+ }
+
+ if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
+ newBoundServiceInfo)) {
+ MyServiceConnection oldServiceConnection = mServiceConnection;
+ MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
+ mServiceConnection = newServiceConnection;
+ mHandler.post(() -> {
+ oldServiceConnection.unbind();
+ newServiceConnection.bind();
+ });
+ }
+ }
+
+ @Override
+ public String toString() {
+ MyServiceConnection serviceConnection;
+ synchronized (this) {
+ serviceConnection = mServiceConnection;
+ }
+
+ return serviceConnection.getBoundServiceInfo().toString();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ MyServiceConnection serviceConnection;
+ synchronized (this) {
+ serviceConnection = mServiceConnection;
+ }
+
+ pw.println("target service=" + serviceConnection.getBoundServiceInfo());
+ pw.println("connected=" + serviceConnection.isConnected());
+ }
+
+ // runs on the handler thread, and expects most of it's methods to be called from that thread
+ private class MyServiceConnection implements ServiceConnection {
+
+ private final @Nullable TBoundServiceInfo mBoundServiceInfo;
+
+ // volatile so that isConnected can be called from any thread easily
+ private volatile @Nullable IBinder mBinder;
+ private @Nullable Runnable mRebinder;
+
+ MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
+ mBoundServiceInfo = boundServiceInfo;
+ }
+
+ // may be called from any thread
+ @Nullable TBoundServiceInfo getBoundServiceInfo() {
+ return mBoundServiceInfo;
+ }
+
+ // may be called from any thread
+ boolean isConnected() {
+ return mBinder != null;
+ }
+
+ void bind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ Log.i(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
+
+ Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
+ mBoundServiceInfo.getComponentName());
+ if (!mContext.bindServiceAsUser(bindIntent, this,
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+ mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+ Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+ mRebinder = this::bind;
+ mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+ } else {
+ mRebinder = null;
+ }
+ }
+
+ void unbind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
+ }
+
+ if (mRebinder != null) {
+ mHandler.removeCallbacks(mRebinder);
+ mRebinder = null;
+ } else {
+ mContext.unbindService(this);
+ }
+
+ onServiceDisconnected(mBoundServiceInfo.getComponentName());
+ }
+
+ void runOnBinder(BinderOperation operation) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ operation.onError();
+ return;
+ }
+
+ try {
+ operation.run(mBinder);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ operation.onError();
+ }
+ }
+
+ @Override
+ public final void onServiceConnected(ComponentName component, IBinder binder) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+ Preconditions.checkState(mBinder == null);
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] connected to " + component.toShortString());
+ }
+
+ mBinder = binder;
+
+ if (mServiceListener != null) {
+ try {
+ mServiceListener.onBind(binder, mBoundServiceInfo);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ }
+ }
+ }
+
+ @Override
+ public final void onServiceDisconnected(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
+ }
+
+ mBinder = null;
+ if (mServiceListener != null) {
+ mServiceListener.onUnbind();
+ }
+ }
+
+ @Override
+ public final void onBindingDied(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ Log.i(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
+
+ onServiceChanged(true);
+ }
+
+ @Override
+ public final void onNullBinding(ComponentName component) {
+ Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index dedb3ac..ff6c2f5 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -27,11 +27,13 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.os.IVold;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
@@ -53,6 +55,7 @@
private final Object mLock = new Object();
private final Context mContext;
+ private final UserManager mUserManager;
@GuardedBy("mLock")
private final SparseArray<StorageUserConnection> mConnections = new SparseArray<>();
@@ -63,6 +66,30 @@
public StorageSessionController(Context context) {
mContext = Objects.requireNonNull(context);
+ mUserManager = mContext.getSystemService(UserManager.class);
+ }
+
+ /**
+ * Returns userId for the volume to be used in the StorageUserConnection.
+ * If the user is a clone profile, it will use the same connection
+ * as the parent user, and hence this method returns the parent's userId. Else, it returns the
+ * volume's mountUserId
+ * @param vol for which the storage session has to be started
+ * @return userId for connection for this volume
+ */
+ public int getConnectionUserIdForVolume(VolumeInfo vol) {
+ final Context volumeUserContext = mContext.createContextAsUser(
+ UserHandle.of(vol.mountUserId), 0);
+ boolean sharesMediaWithParent = volumeUserContext.getSystemService(
+ UserManager.class).sharesMediaWithParent();
+
+ UserInfo userInfo = mUserManager.getUserInfo(vol.mountUserId);
+ if (userInfo != null && sharesMediaWithParent) {
+ // Clones use the same connection as their parent
+ return userInfo.profileGroupId;
+ } else {
+ return vol.mountUserId;
+ }
}
/**
@@ -88,7 +115,7 @@
Slog.i(TAG, "On volume mount " + vol);
String sessionId = vol.getId();
- int userId = vol.getMountUserId();
+ int userId = getConnectionUserIdForVolume(vol);
StorageUserConnection connection = null;
synchronized (mLock) {
@@ -120,7 +147,7 @@
return;
}
String sessionId = vol.getId();
- int userId = vol.getMountUserId();
+ int userId = getConnectionUserIdForVolume(vol);
StorageUserConnection connection = null;
synchronized (mLock) {
@@ -191,7 +218,7 @@
Slog.i(TAG, "On volume remove " + vol);
String sessionId = vol.getId();
- int userId = vol.getMountUserId();
+ int userId = getConnectionUserIdForVolume(vol);
synchronized (mLock) {
StorageUserConnection connection = mConnections.get(userId);
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index a0e2286..0b11b0b 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -265,6 +265,10 @@
synchronized (mSessionsLock) {
int ioBlockedCounter = mUidsBlockedOnIo.get(uid, 0);
if (ioBlockedCounter == 0) {
+ Slog.w(TAG, "Unexpected app IO resumption for uid: " + uid);
+ }
+
+ if (ioBlockedCounter <= 1) {
mUidsBlockedOnIo.remove(uid);
} else {
mUidsBlockedOnIo.put(uid, --ioBlockedCounter);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index eefa045a..27e2ee5 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -228,7 +228,7 @@
private void enforceSuggestExternalTimePermission() {
// We don't expect a call from system server, so simply enforce calling permission.
mContext.enforceCallingPermission(
- android.Manifest.permission.SET_TIME,
+ android.Manifest.permission.SUGGEST_EXTERNAL_TIME,
"suggest time from external source");
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
index 0b51488..6c3f016 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
@@ -16,19 +16,14 @@
package com.android.server.timezonedetector.location;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
+import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
-
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -39,11 +34,12 @@
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
-import java.util.function.Predicate;
/**
* System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the
@@ -52,7 +48,8 @@
* different process. As "remote" providers are bound / unbound this proxy will rebind to the "best"
* available remote process.
*/
-class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
+class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy implements
+ ServiceListener<BoundServiceInfo> {
@NonNull private final ServiceWatcher mServiceWatcher;
@@ -69,38 +66,13 @@
super(context, threadingDomain);
mManagerProxy = null;
mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
-
- // A predicate that is used to confirm that an intent service can be used as a
- // location-based TimeZoneProvider. The service must:
- // 1) Declare android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE" - this
- // ensures that the provider will only communicate with the system server.
- // 2) Be in an application that has been granted the
- // android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE permission. This
- // ensures only trusted time zone providers will be discovered.
- final String requiredClientPermission = Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
- final String requiredPermission =
- Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
- Predicate<ResolveInfo> intentServiceCheckPredicate = resolveInfo -> {
- ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-
- boolean hasClientPermissionRequirement =
- requiredClientPermission.equals(serviceInfo.permission);
-
- String packageName = serviceInfo.packageName;
- PackageManager packageManager = context.getPackageManager();
- int checkResult = packageManager.checkPermission(requiredPermission, packageName);
- boolean hasRequiredPermission = checkResult == PERMISSION_GRANTED;
-
- boolean result = hasClientPermissionRequirement && hasRequiredPermission;
- if (!result) {
- warnLog("resolveInfo=" + resolveInfo + " does not meet requirements:"
- + " hasClientPermissionRequirement=" + hasClientPermissionRequirement
- + ", hasRequiredPermission=" + hasRequiredPermission);
- }
- return result;
- };
- mServiceWatcher = new ServiceWatcher(context, handler, action, this::onBind, this::onUnbind,
- enableOverlayResId, nonOverlayPackageResId, intentServiceCheckPredicate);
+ mServiceWatcher = ServiceWatcher.create(context,
+ handler,
+ "RealLocationTimeZoneProviderProxy",
+ new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+ nonOverlayPackageResId, BIND_TIME_ZONE_PROVIDER_SERVICE,
+ INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE),
+ this);
}
@Override
@@ -123,7 +95,8 @@
return resolves;
}
- private void onBind(IBinder binder, BoundService boundService) {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundService) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -138,7 +111,8 @@
}
}
- private void onUnbind() {
+ @Override
+ public void onUnbind() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -199,7 +173,7 @@
synchronized (mSharedLock) {
ipw.println("{RealLocationTimeZoneProviderProxy}");
ipw.println("mRequest=" + mRequest);
- mServiceWatcher.dump(null, ipw, args);
+ mServiceWatcher.dump(ipw);
}
}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 3f74938..89ed956 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -41,6 +41,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -110,6 +111,24 @@
@NonNull private final VcnNetworkRequestListener mRequestListener;
@NonNull private final VcnCallback mVcnCallback;
+ /**
+ * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
+ *
+ * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added
+ * to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives
+ * a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig.
+ *
+ * <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise
+ * there is potential for a orphaned VcnGatewayConnection instance that does not get properly
+ * shut down.
+ *
+ * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this
+ * map once they have finished tearing down, which is reported to this VCN via {@link
+ * VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from
+ * the NetworkProvider so that another VcnGatewayConnectionConfig can match the
+ * previously-matched request.
+ */
+ // TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles
@NonNull
private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
new HashMap<>();
@@ -191,6 +210,19 @@
return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
}
+ /** Get current Configs and Gateways for testing purposes */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
+ getVcnGatewayConnectionConfigMap() {
+ return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections));
+ }
+
+ /** Set whether this Vcn is active for testing purposes */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public void setIsActive(boolean isActive) {
+ mIsActive.set(isActive);
+ }
+
private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
@Override
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
@@ -202,11 +234,6 @@
@Override
public void handleMessage(@NonNull Message msg) {
- // Ignore if this Vcn is not active and we're not receiving new configs
- if (!isActive() && msg.what != MSG_EVENT_CONFIG_UPDATED) {
- return;
- }
-
switch (msg.what) {
case MSG_EVENT_CONFIG_UPDATED:
handleConfigUpdated((VcnConfig) msg.obj);
@@ -237,9 +264,31 @@
mConfig = config;
- // TODO(b/181815405): Reevaluate active VcnGatewayConnection(s)
+ if (mIsActive.getAndSet(true)) {
+ // VCN is already active - teardown any GatewayConnections whose configs have been
+ // removed and get all current requests
+ for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
+ mVcnGatewayConnections.entrySet()) {
+ final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
+ final VcnGatewayConnection gatewayConnection = entry.getValue();
- if (!mIsActive.getAndSet(true)) {
+ // GatewayConnectionConfigs must match exactly (otherwise authentication or
+ // connection details may have changed).
+ if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
+ if (gatewayConnection == null) {
+ Slog.wtf(
+ getLogTag(),
+ "Found gatewayConnectionConfig without GatewayConnection");
+ } else {
+ gatewayConnection.teardownAsynchronously();
+ }
+ }
+ }
+
+ // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
+ // satisfied start a new GatewayConnection)
+ mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+ } else {
// If this VCN was not previously active, it is exiting Safe Mode. Re-register the
// request listener to get NetworkRequests again (and all cached requests).
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
@@ -259,13 +308,16 @@
private void handleEnterSafeMode() {
handleTeardown();
- mVcnGatewayConnections.clear();
-
mVcnCallback.onEnteredSafeMode();
}
private void handleNetworkRequested(
@NonNull NetworkRequest request, int score, int providerId) {
+ if (!isActive()) {
+ Slog.v(getLogTag(), "Received NetworkRequest while inactive. Ignore for now");
+ return;
+ }
+
if (score > getNetworkScore()) {
if (VDBG) {
Slog.v(
@@ -318,8 +370,10 @@
mVcnGatewayConnections.remove(config);
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
- // start a new GatewayConnection)
- mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+ // start a new GatewayConnection), but only if the Vcn is still active
+ if (isActive()) {
+ mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+ }
}
private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
new file mode 100644
index 0000000..953837a
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 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.vibrator;
+
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.MathUtils;
+import android.util.Range;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
+final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> {
+
+ /**
+ * Adapts a sequence of {@link VibrationEffectSegment} to device's absolute frequency values
+ * and respective supported amplitudes.
+ *
+ * <p>This adapter preserves the segment count.
+ */
+ interface AmplitudeFrequencyAdapter {
+ List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments,
+ VibratorInfo info);
+ }
+
+ private final AmplitudeFrequencyAdapter mAmplitudeFrequencyAdapter;
+
+ DeviceVibrationEffectAdapter() {
+ this(new ClippingAmplitudeFrequencyAdapter());
+ }
+
+ DeviceVibrationEffectAdapter(AmplitudeFrequencyAdapter amplitudeFrequencyAdapter) {
+ mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter;
+ }
+
+ @Override
+ public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) {
+ if (!(effect instanceof VibrationEffect.Composed)) {
+ return effect;
+ }
+
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ List<VibrationEffectSegment> mappedSegments = mAmplitudeFrequencyAdapter.apply(
+ composed.getSegments(), info);
+
+ // TODO(b/167947076): add ramp to step adapter once PWLE capability is introduced
+ // TODO(b/167947076): add filter that removes unsupported primitives
+ // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
+
+ return new VibrationEffect.Composed(mappedSegments, composed.getRepeatIndex());
+ }
+
+ /**
+ * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
+ * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
+ *
+ * <p>Devices with no frequency control will collapse all frequencies to zero and leave
+ * amplitudes unchanged.
+ */
+ private static final class ClippingAmplitudeFrequencyAdapter
+ implements AmplitudeFrequencyAdapter {
+ @Override
+ public List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments,
+ VibratorInfo info) {
+ List<VibrationEffectSegment> result = new ArrayList<>();
+ int segmentCount = segments.size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = segments.get(i);
+ if (segment instanceof StepSegment) {
+ result.add(apply((StepSegment) segment, info));
+ } else if (segment instanceof RampSegment) {
+ result.add(apply((RampSegment) segment, info));
+ } else {
+ result.add(segment);
+ }
+ }
+ return result;
+ }
+
+ private StepSegment apply(StepSegment segment, VibratorInfo info) {
+ float clampedFrequency = info.getFrequencyRange().clamp(segment.getFrequency());
+ return new StepSegment(
+ MathUtils.min(segment.getAmplitude(), info.getMaxAmplitude(clampedFrequency)),
+ info.getAbsoluteFrequency(clampedFrequency),
+ (int) segment.getDuration());
+ }
+
+ private RampSegment apply(RampSegment segment, VibratorInfo info) {
+ Range<Float> frequencyRange = info.getFrequencyRange();
+ float clampedStartFrequency = frequencyRange.clamp(segment.getStartFrequency());
+ float clampedEndFrequency = frequencyRange.clamp(segment.getEndFrequency());
+ return new RampSegment(
+ MathUtils.min(segment.getStartAmplitude(),
+ info.getMaxAmplitude(clampedStartFrequency)),
+ MathUtils.min(segment.getEndAmplitude(),
+ info.getMaxAmplitude(clampedEndFrequency)),
+ info.getAbsoluteFrequency(clampedStartFrequency),
+ info.getAbsoluteFrequency(clampedEndFrequency),
+ (int) segment.getDuration());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e84ee672..cd84058 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,17 +16,24 @@
package com.android.server.vibrator;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibrationEffect;
import android.os.IBinder;
import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
/** Represents a vibration request to the vibrator service. */
final class Vibration {
@@ -61,6 +68,7 @@
public final String opPkg;
public final String reason;
public final IBinder token;
+ public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
/** The actual effect to be played. */
@Nullable
@@ -113,17 +121,70 @@
}
/**
- * Replace this vibration effect if given {@code scaledEffect} is different, preserving the
- * original one for debug purposes.
+ * Return the effect to be played when given prebaked effect id is not supported by the
+ * vibrator.
*/
- public void updateEffect(@NonNull CombinedVibrationEffect newEffect) {
- if (newEffect.equals(mEffect)) {
- return;
+ @Nullable
+ public VibrationEffect getFallback(int effectId) {
+ return mFallbacks.get(effectId);
+ }
+
+ /**
+ * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
+ * which might be necessary for replacement in realtime.
+ */
+ public void addFallback(int effectId, VibrationEffect effect) {
+ mFallbacks.put(effectId, effect);
+ }
+
+ /**
+ * Applied update function to the current effect held by this vibration, and to each fallback
+ * effect added.
+ */
+ public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
+ CombinedVibrationEffect newEffect = transformCombinedEffect(mEffect, updateFn);
+ if (!newEffect.equals(mEffect)) {
+ if (mOriginalEffect == null) {
+ mOriginalEffect = mEffect;
+ }
+ mEffect = newEffect;
}
- if (mOriginalEffect == null) {
- mOriginalEffect = mEffect;
+ for (int i = 0; i < mFallbacks.size(); i++) {
+ mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
}
- mEffect = newEffect;
+ }
+
+ /**
+ * Creates a new {@link CombinedVibrationEffect} by applying the given transformation function
+ * to each {@link VibrationEffect}.
+ */
+ private static CombinedVibrationEffect transformCombinedEffect(
+ CombinedVibrationEffect combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
+ if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
+ VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
+ return CombinedVibrationEffect.createSynced(fn.apply(effect));
+ } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects();
+ CombinedVibrationEffect.SyncedCombination combination =
+ CombinedVibrationEffect.startSynced();
+ for (int i = 0; i < effects.size(); i++) {
+ combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
+ }
+ return combination.combine();
+ } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
+ List<CombinedVibrationEffect> effects =
+ ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects();
+ CombinedVibrationEffect.SequentialCombination combination =
+ CombinedVibrationEffect.startSequential();
+ for (CombinedVibrationEffect effect : effects) {
+ combination.addNext(transformCombinedEffect(effect, fn));
+ }
+ return combination.combine();
+ } else {
+ // Unknown combination, return same effect.
+ return combinedEffect;
+ }
}
/** Return true is current status is different from {@link Status#RUNNING}. */
@@ -272,57 +333,62 @@
private void dumpEffect(
ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
final long token = proto.start(fieldId);
- if (effect instanceof VibrationEffect.OneShot) {
- dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
- } else if (effect instanceof VibrationEffect.Waveform) {
- dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect);
- } else if (effect instanceof VibrationEffect.Prebaked) {
- dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect);
- } else if (effect instanceof VibrationEffect.Composed) {
- dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect);
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ for (VibrationEffectSegment segment : composed.getSegments()) {
+ dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
}
+ proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
proto.end(token);
}
private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.OneShot effect) {
+ VibrationEffectSegment segment) {
final long token = proto.start(fieldId);
- proto.write(OneShotProto.DURATION, (int) effect.getDuration());
- proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude());
+ if (segment instanceof StepSegment) {
+ dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment);
+ } else if (segment instanceof RampSegment) {
+ dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment);
+ } else if (segment instanceof PrebakedSegment) {
+ dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment);
+ } else if (segment instanceof PrimitiveSegment) {
+ dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment);
+ }
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) {
+ final long token = proto.start(fieldId);
+ proto.write(StepSegmentProto.DURATION, segment.getDuration());
+ proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude());
+ proto.write(StepSegmentProto.FREQUENCY, segment.getFrequency());
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) {
+ final long token = proto.start(fieldId);
+ proto.write(RampSegmentProto.DURATION, segment.getDuration());
+ proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude());
+ proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude());
+ proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequency());
+ proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequency());
proto.end(token);
}
private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.Waveform effect) {
+ PrebakedSegment segment) {
final long token = proto.start(fieldId);
- for (long timing : effect.getTimings()) {
- proto.write(WaveformProto.TIMINGS, (int) timing);
- }
- for (int amplitude : effect.getAmplitudes()) {
- proto.write(WaveformProto.AMPLITUDES, amplitude);
- }
- proto.write(WaveformProto.REPEAT, effect.getRepeatIndex() >= 0);
+ proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId());
+ proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength());
+ proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback());
proto.end(token);
}
private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.Prebaked effect) {
+ PrimitiveSegment segment) {
final long token = proto.start(fieldId);
- proto.write(PrebakedProto.EFFECT_ID, effect.getId());
- proto.write(PrebakedProto.EFFECT_STRENGTH, effect.getEffectStrength());
- proto.write(PrebakedProto.FALLBACK, effect.shouldFallback());
- proto.end(token);
- }
-
- private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.Composed effect) {
- final long token = proto.start(fieldId);
- for (VibrationEffect.Composition.PrimitiveEffect primitive :
- effect.getPrimitiveEffects()) {
- proto.write(ComposedProto.EFFECT_IDS, primitive.id);
- proto.write(ComposedProto.EFFECT_SCALES, primitive.scale);
- proto.write(ComposedProto.DELAYS, primitive.delay);
- }
+ proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId());
+ proto.write(PrimitiveSegmentProto.SCALE, segment.getScale());
+ proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
proto.end(token);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java
new file mode 100644
index 0000000..d287c8f
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 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.vibrator;
+
+import android.os.VibrationEffect;
+
+/** Function that applies a generic modifier to a {@link VibrationEffect}. */
+interface VibrationEffectModifier<T> {
+
+ /** Applies the modifier to given {@link VibrationEffect}. */
+ VibrationEffect apply(VibrationEffect effect, T modifier);
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 10393f6..f481772 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -18,16 +18,13 @@
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
-import android.os.CombinedVibrationEffect;
import android.os.IExternalVibratorService;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.vibrator.PrebakedSegment;
import android.util.Slog;
import android.util.SparseArray;
-import java.util.List;
-import java.util.Objects;
-
/** Controls vibration scaling. */
final class VibrationScaler {
private static final String TAG = "VibrationScaler";
@@ -90,43 +87,6 @@
}
/**
- * Scale a {@link CombinedVibrationEffect} based on the given usage hint for this vibration.
- *
- * @param combinedEffect the effect to be scaled
- * @param usageHint one of VibrationAttributes.USAGE_*
- * @return The same given effect, if no changes were made, or a new
- * {@link CombinedVibrationEffect} with resolved and scaled amplitude
- */
- public <T extends CombinedVibrationEffect> T scale(CombinedVibrationEffect combinedEffect,
- int usageHint) {
- if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
- VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
- return (T) CombinedVibrationEffect.createSynced(scale(effect, usageHint));
- } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
- SparseArray<VibrationEffect> effects =
- ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects();
- CombinedVibrationEffect.SyncedCombination combination =
- CombinedVibrationEffect.startSynced();
- for (int i = 0; i < effects.size(); i++) {
- combination.addVibrator(effects.keyAt(i), scale(effects.valueAt(i), usageHint));
- }
- return (T) combination.combine();
- } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
- List<CombinedVibrationEffect> effects =
- ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects();
- CombinedVibrationEffect.SequentialCombination combination =
- CombinedVibrationEffect.startSequential();
- for (CombinedVibrationEffect effect : effects) {
- combination.addNext(scale(effect, usageHint));
- }
- return (T) combination.combine();
- } else {
- // Unknown combination, return same effect.
- return (T) combinedEffect;
- }
- }
-
- /**
* Scale a {@link VibrationEffect} based on the given usage hint for this vibration.
*
* @param effect the effect to be scaled
@@ -135,33 +95,10 @@
* resolved and scaled amplitude
*/
public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) {
- if (effect instanceof VibrationEffect.Prebaked) {
- // Prebaked effects are always just a direct translation to EffectStrength.
- int intensity = mSettingsController.getCurrentIntensity(usageHint);
- int newStrength = intensityToEffectStrength(intensity);
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- int strength = prebaked.getEffectStrength();
- VibrationEffect fallback = prebaked.getFallbackEffect();
-
- if (fallback != null) {
- VibrationEffect scaledFallback = scale(fallback, usageHint);
- if (strength == newStrength && Objects.equals(fallback, scaledFallback)) {
- return (T) prebaked;
- }
-
- return (T) new VibrationEffect.Prebaked(prebaked.getId(), newStrength,
- scaledFallback);
- } else if (strength == newStrength) {
- return (T) prebaked;
- } else {
- return (T) new VibrationEffect.Prebaked(prebaked.getId(), prebaked.shouldFallback(),
- newStrength);
- }
- }
-
- effect = effect.resolve(mDefaultVibrationAmplitude);
int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
+ int newEffectStrength = intensityToEffectStrength(currentIntensity);
+ effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude);
ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity);
if (scale == null) {
@@ -171,7 +108,21 @@
return (T) effect;
}
- return effect.scale(scale.factor);
+ return (T) effect.scale(scale.factor);
+ }
+
+ /**
+ * Scale a {@link PrebakedSegment} based on the given usage hint for this vibration.
+ *
+ * @param prebaked the prebaked segment to be scaled
+ * @param usageHint one of VibrationAttributes.USAGE_*
+ * @return The same segment if no changes were made, or a new {@link PrebakedSegment} with
+ * updated effect strength
+ */
+ public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) {
+ int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
+ int newEffectStrength = intensityToEffectStrength(currentIntensity);
+ return prebaked.applyEffectStrength(newEffectStrength);
}
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index b90408f..3090e6d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -27,7 +27,13 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
+import android.os.VibratorInfo;
import android.os.WorkSource;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import android.util.SparseArray;
@@ -87,6 +93,8 @@
private final WorkSource mWorkSource = new WorkSource();
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
+ private final VibrationEffectModifier<VibratorInfo> mDeviceEffectAdapter =
+ new DeviceVibrationEffectAdapter();
private final Vibration mVibration;
private final VibrationCallbacks mCallbacks;
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
@@ -248,22 +256,26 @@
* Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
* startIndex} until the next time it's vibrating amplitude is zero.
*/
- private static long getVibratorOnDuration(VibrationEffect.Waveform waveform, int startIndex) {
- long[] timings = waveform.getTimings();
- int[] amplitudes = waveform.getAmplitudes();
- int repeatIndex = waveform.getRepeatIndex();
+ private static long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
+ List<VibrationEffectSegment> segments = effect.getSegments();
+ int segmentCount = segments.size();
+ int repeatIndex = effect.getRepeatIndex();
int i = startIndex;
long timing = 0;
- while (timings[i] == 0 || amplitudes[i] != 0) {
- timing += timings[i++];
- if (i >= timings.length) {
- if (repeatIndex >= 0) {
- i = repeatIndex;
- // prevent infinite loop
- repeatIndex = -1;
- } else {
- break;
- }
+ while (i < segmentCount) {
+ if (!(segments.get(i) instanceof StepSegment)) {
+ break;
+ }
+ StepSegment stepSegment = (StepSegment) segments.get(i);
+ if (stepSegment.getAmplitude() == 0) {
+ break;
+ }
+ timing += stepSegment.getDuration();
+ i++;
+ if (i == segmentCount && repeatIndex >= 0) {
+ i = repeatIndex;
+ // prevent infinite loop
+ repeatIndex = -1;
}
if (i == startIndex) {
return 1000;
@@ -620,22 +632,19 @@
}
private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+ // TODO(b/167947076): split this into 4 different step implementations:
+ // VibratorPerformStep, VibratorComposePrimitiveStep, VibratorComposePwleStep and
+ // VibratorAmplitudeStep.
+ // Make sure each step carries over the full VibrationEffect and an incremental segment
+ // index, and triggers a final VibratorOffStep once all segments are done.
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ VibrationEffectSegment firstSegment = composed.getSegments().get(0);
final long duration;
final long now = SystemClock.uptimeMillis();
- if (effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- duration = oneShot.getDuration();
- // Do NOT set amplitude here. This might be called between prepareSynced and
- // triggerSynced, so the vibrator is not actually turned on here.
- // The next steps will handle the amplitude after the vibrator has turned on.
- controller.on(duration, mVibration.id);
- nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot,
- now + duration + CALLBACKS_EXTRA_TIMEOUT));
- } else if (effect instanceof VibrationEffect.Waveform) {
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ if (firstSegment instanceof StepSegment) {
// Return the full duration of this waveform effect.
- duration = waveform.getDuration();
- long onDuration = getVibratorOnDuration(waveform, 0);
+ duration = effect.getDuration();
+ long onDuration = getVibratorOnDuration(composed, 0);
if (onDuration > 0) {
// Do NOT set amplitude here. This might be called between prepareSynced and
// triggerSynced, so the vibrator is not actually turned on here.
@@ -643,19 +652,53 @@
controller.on(onDuration, mVibration.id);
}
long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now;
- nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime));
- } else if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ nextSteps.add(new VibratorAmplitudeStep(now, controller, composed, offTime));
+ } else if (firstSegment instanceof PrebakedSegment) {
+ PrebakedSegment prebaked = (PrebakedSegment) firstSegment;
+ VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId());
duration = controller.on(prebaked, mVibration.id);
if (duration > 0) {
nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
controller));
- } else if (prebaked.getFallbackEffect() != null) {
- return startVibrating(prebaked.getFallbackEffect(), nextSteps);
+ } else if (prebaked.shouldFallback() && fallback != null) {
+ return startVibrating(fallback, nextSteps);
}
- } else if (effect instanceof VibrationEffect.Composed) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
- duration = controller.on(composed, mVibration.id);
+ } else if (firstSegment instanceof PrimitiveSegment) {
+ int segmentCount = composed.getSegments().size();
+ PrimitiveSegment[] primitives = new PrimitiveSegment[segmentCount];
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if (segment instanceof PrimitiveSegment) {
+ primitives[i] = (PrimitiveSegment) segment;
+ } else {
+ primitives[i] = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_NOOP,
+ /* scale= */ 1, /* delay= */ 0);
+ }
+ }
+ duration = controller.on(primitives, mVibration.id);
+ if (duration > 0) {
+ nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
+ controller));
+ }
+ } else if (firstSegment instanceof RampSegment) {
+ int segmentCount = composed.getSegments().size();
+ RampSegment[] primitives = new RampSegment[segmentCount];
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if (segment instanceof RampSegment) {
+ primitives[i] = (RampSegment) segment;
+ } else if (segment instanceof StepSegment) {
+ StepSegment stepSegment = (StepSegment) segment;
+ primitives[i] = new RampSegment(
+ stepSegment.getAmplitude(), stepSegment.getAmplitude(),
+ stepSegment.getFrequency(), stepSegment.getFrequency(),
+ (int) stepSegment.getDuration());
+ } else {
+ primitives[i] = new RampSegment(0, 0, 0, 0, 0);
+ }
+ }
+ duration = controller.on(primitives, mVibration.id);
if (duration > 0) {
nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
controller));
@@ -713,33 +756,22 @@
/** Represents a step to change the amplitude of the vibrator. */
private final class VibratorAmplitudeStep extends Step {
public final VibratorController controller;
- public final VibrationEffect.Waveform waveform;
+ public final VibrationEffect.Composed effect;
public final int currentIndex;
- public final long expectedVibratorStopTime;
private long mNextVibratorStopTime;
VibratorAmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) {
- this(startTime, controller,
- (VibrationEffect.Waveform) VibrationEffect.createWaveform(
- new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()},
- /* repeat= */ -1),
- expectedVibratorStopTime);
+ VibrationEffect.Composed effect, long expectedVibratorStopTime) {
+ this(startTime, controller, effect, /* index= */ 0, expectedVibratorStopTime);
}
VibratorAmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.Waveform waveform, long expectedVibratorStopTime) {
- this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime);
- }
-
- VibratorAmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) {
+ VibrationEffect.Composed effect, int index, long expectedVibratorStopTime) {
super(startTime);
this.controller = controller;
- this.waveform = waveform;
+ this.effect = effect;
this.currentIndex = index;
- this.expectedVibratorStopTime = expectedVibratorStopTime;
mNextVibratorStopTime = expectedVibratorStopTime;
}
@@ -759,11 +791,16 @@
long latency = SystemClock.uptimeMillis() - startTime;
Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
}
- if (waveform.getTimings()[currentIndex] == 0) {
+ VibrationEffectSegment segment = effect.getSegments().get(currentIndex);
+ if (!(segment instanceof StepSegment)) {
+ return nextSteps();
+ }
+ StepSegment stepSegment = (StepSegment) segment;
+ if (stepSegment.getDuration() == 0) {
// Skip waveform entries with zero timing.
return nextSteps();
}
- int amplitude = waveform.getAmplitudes()[currentIndex];
+ float amplitude = stepSegment.getAmplitude();
if (amplitude == 0) {
stopVibrating();
return nextSteps();
@@ -771,7 +808,7 @@
if (startTime >= mNextVibratorStopTime) {
// Vibrator has stopped. Turn vibrator back on for the duration of another
// cycle before setting the amplitude.
- long onDuration = getVibratorOnDuration(waveform, currentIndex);
+ long onDuration = getVibratorOnDuration(effect, currentIndex);
if (onDuration > 0) {
startVibrating(onDuration);
mNextVibratorStopTime =
@@ -806,7 +843,7 @@
controller.on(duration, mVibration.id);
}
- private void changeAmplitude(int amplitude) {
+ private void changeAmplitude(float amplitude) {
if (DEBUG) {
Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
+ " to " + amplitude);
@@ -816,16 +853,16 @@
@NonNull
private List<Step> nextSteps() {
- long nextStartTime = startTime + waveform.getTimings()[currentIndex];
+ long nextStartTime = startTime + effect.getSegments().get(currentIndex).getDuration();
int nextIndex = currentIndex + 1;
- if (nextIndex >= waveform.getTimings().length) {
- nextIndex = waveform.getRepeatIndex();
+ if (nextIndex >= effect.getSegments().size()) {
+ nextIndex = effect.getRepeatIndex();
}
- if (nextIndex < 0) {
- return Arrays.asList(new VibratorOffStep(nextStartTime, controller));
- }
- return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform,
- nextIndex, mNextVibratorStopTime));
+ Step nextStep = nextIndex < 0
+ ? new VibratorOffStep(nextStartTime, controller)
+ : new VibratorAmplitudeStep(nextStartTime, controller, effect, nextIndex,
+ mNextVibratorStopTime);
+ return Arrays.asList(nextStep);
}
}
@@ -845,7 +882,9 @@
mVibratorIds = new int[mVibrators.size()];
for (int i = 0; i < mVibrators.size(); i++) {
int vibratorId = mVibrators.keyAt(i);
- mVibratorEffects.put(vibratorId, mono.getEffect());
+ VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
+ VibrationEffect effect = mDeviceEffectAdapter.apply(mono.getEffect(), vibratorInfo);
+ mVibratorEffects.put(vibratorId, effect);
mVibratorIds[i] = vibratorId;
}
mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
@@ -857,7 +896,10 @@
for (int i = 0; i < stereoEffects.size(); i++) {
int vibratorId = stereoEffects.keyAt(i);
if (mVibrators.contains(vibratorId)) {
- mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i));
+ VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
+ VibrationEffect effect = mDeviceEffectAdapter.apply(
+ stereoEffects.valueAt(i), vibratorInfo);
+ mVibratorEffects.put(vibratorId, effect);
}
}
mVibratorIds = new int[mVibratorEffects.size()];
@@ -909,13 +951,13 @@
private long calculateRequiredSyncCapabilities(SparseArray<VibrationEffect> effects) {
long prepareCap = 0;
for (int i = 0; i < effects.size(); i++) {
- VibrationEffect effect = effects.valueAt(i);
- if (effect instanceof VibrationEffect.OneShot
- || effect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effects.valueAt(i);
+ VibrationEffectSegment firstSegment = composed.getSegments().get(0);
+ if (firstSegment instanceof StepSegment) {
prepareCap |= IVibratorManager.CAP_PREPARE_ON;
- } else if (effect instanceof VibrationEffect.Prebaked) {
+ } else if (firstSegment instanceof PrebakedSegment) {
prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
- } else if (effect instanceof VibrationEffect.Composed) {
+ } else if (firstSegment instanceof PrimitiveSegment) {
prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index e3dc70b..c897702 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -22,8 +22,10 @@
import android.os.IVibratorStateListener;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -64,9 +66,12 @@
mNativeWrapper = nativeWrapper;
mNativeWrapper.init(vibratorId, listener);
+ // TODO(b/167947076): load supported ones from HAL once API introduced
+ VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
+ Float.NaN, nativeWrapper.getResonantFrequency(), Float.NaN, Float.NaN, null);
mVibratorInfo = new VibratorInfo(vibratorId, nativeWrapper.getCapabilities(),
nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives(),
- nativeWrapper.getResonantFrequency(), nativeWrapper.getQFactor());
+ nativeWrapper.getQFactor(), frequencyMapping);
}
/** Register state listener for this vibrator. */
@@ -156,21 +161,22 @@
* Update the predefined vibration effect saved with given id. This will remove the saved effect
* if given {@code effect} is {@code null}.
*/
- public void updateAlwaysOn(int id, @Nullable VibrationEffect.Prebaked effect) {
+ public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
return;
}
synchronized (mLock) {
- if (effect == null) {
+ if (prebaked == null) {
mNativeWrapper.alwaysOnDisable(id);
} else {
- mNativeWrapper.alwaysOnEnable(id, effect.getId(), effect.getEffectStrength());
+ mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(),
+ prebaked.getEffectStrength());
}
}
}
/** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
- public void setAmplitude(int amplitude) {
+ public void setAmplitude(float amplitude) {
synchronized (mLock) {
if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
mNativeWrapper.setAmplitude(amplitude);
@@ -199,10 +205,10 @@
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(VibrationEffect.Prebaked effect, long vibrationId) {
+ public long on(PrebakedSegment prebaked, long vibrationId) {
synchronized (mLock) {
- long duration = mNativeWrapper.perform(effect.getId(), effect.getEffectStrength(),
- vibrationId);
+ long duration = mNativeWrapper.perform(prebaked.getEffectId(),
+ prebaked.getEffectStrength(), vibrationId);
if (duration > 0) {
notifyVibratorOnLocked();
}
@@ -211,21 +217,18 @@
}
/**
- * Plays composited vibration effect, using {@code vibrationId} or completion callback to
- * {@link OnVibrationCompleteListener}.
+ * Plays a composition of vibration primitives, using {@code vibrationId} or completion callback
+ * to {@link OnVibrationCompleteListener}.
*
* <p>This will affect the state of {@link #isVibrating()}.
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(VibrationEffect.Composed effect, long vibrationId) {
+ public long on(PrimitiveSegment[] primitives, long vibrationId) {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
return 0;
}
synchronized (mLock) {
- VibrationEffect.Composition.PrimitiveEffect[] primitives =
- effect.getPrimitiveEffects().toArray(
- new VibrationEffect.Composition.PrimitiveEffect[0]);
long duration = mNativeWrapper.compose(primitives, vibrationId);
if (duration > 0) {
notifyVibratorOnLocked();
@@ -234,6 +237,19 @@
}
}
+ /**
+ * Plays a composition of pwle primitives, using {@code vibrationId} or completion callback
+ * to {@link OnVibrationCompleteListener}.
+ *
+ * <p>This will affect the state of {@link #isVibrating()}.
+ *
+ * @return The duration of the effect playing, or 0 if unsupported.
+ */
+ public long on(RampSegment[] primitives, long vibrationId) {
+ // TODO(b/167947076): forward to the HAL once APIs are introduced
+ return 0;
+ }
+
/** Turns off the vibrator.This will affect the state of {@link #isVibrating()}. */
public void off() {
synchronized (mLock) {
@@ -313,13 +329,13 @@
private static native boolean isAvailable(long nativePtr);
private static native void on(long nativePtr, long milliseconds, long vibrationId);
private static native void off(long nativePtr);
- private static native void setAmplitude(long nativePtr, int amplitude);
+ private static native void setAmplitude(long nativePtr, float amplitude);
private static native int[] getSupportedEffects(long nativePtr);
private static native int[] getSupportedPrimitives(long nativePtr);
- private static native long performEffect(
- long nativePtr, long effect, long strength, long vibrationId);
- private static native long performComposedEffect(long nativePtr,
- VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId);
+ private static native long performEffect(long nativePtr, long effect, long strength,
+ long vibrationId);
+ private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
+ long vibrationId);
private static native void setExternalControl(long nativePtr, boolean enabled);
private static native long getCapabilities(long nativePtr);
private static native void alwaysOnEnable(long nativePtr, long id, long effect,
@@ -359,7 +375,7 @@
}
/** Sets the amplitude for the vibrator to run. */
- public void setAmplitude(int amplitude) {
+ public void setAmplitude(float amplitude) {
setAmplitude(mNativePtr, amplitude);
}
@@ -379,9 +395,8 @@
}
/** Turns vibrator on to perform one of the supported composed effects. */
- public long compose(
- VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) {
- return performComposedEffect(mNativePtr, effect, vibrationId);
+ public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+ return performComposedEffect(mNativePtr, primitives, vibrationId);
}
/** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c9751bb..5fd1d7a 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -48,6 +48,8 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -312,7 +314,7 @@
}
attrs = fixupVibrationAttributes(attrs);
synchronized (mLock) {
- SparseArray<VibrationEffect.Prebaked> effects = fixupAlwaysOnEffectsLocked(effect);
+ SparseArray<PrebakedSegment> effects = fixupAlwaysOnEffectsLocked(effect);
if (effects == null) {
// Invalid effects set in CombinedVibrationEffect, or always-on capability is
// missing on individual vibrators.
@@ -347,8 +349,7 @@
attrs = fixupVibrationAttributes(attrs);
Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
uid, opPkg, reason);
- // Update with fixed up effect to keep the original effect in Vibration for debugging.
- vib.updateEffect(fixupVibrationEffect(effect));
+ fillVibrationFallbacks(vib, effect);
synchronized (mLock) {
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
@@ -476,7 +477,7 @@
private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
for (int i = 0; i < vib.effects.size(); i++) {
VibratorController vibrator = mVibrators.get(vib.effects.keyAt(i));
- VibrationEffect.Prebaked effect = vib.effects.valueAt(i);
+ PrebakedSegment effect = vib.effects.valueAt(i);
if (vibrator == null) {
continue;
}
@@ -496,7 +497,7 @@
private Vibration.Status startVibrationLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
+ vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage()));
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
if (inputDevicesAvailable) {
@@ -757,43 +758,38 @@
* Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
* VibrationSettings#getFallbackEffect}.
*/
- private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
+ private void fillVibrationFallbacks(Vibration vib, CombinedVibrationEffect effect) {
if (effect instanceof CombinedVibrationEffect.Mono) {
- return CombinedVibrationEffect.createSynced(
- fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
+ fillVibrationFallbacks(vib, ((CombinedVibrationEffect.Mono) effect).getEffect());
} else if (effect instanceof CombinedVibrationEffect.Stereo) {
- CombinedVibrationEffect.SyncedCombination combination =
- CombinedVibrationEffect.startSynced();
SparseArray<VibrationEffect> effects =
((CombinedVibrationEffect.Stereo) effect).getEffects();
for (int i = 0; i < effects.size(); i++) {
- combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
+ fillVibrationFallbacks(vib, effects.valueAt(i));
}
- return combination.combine();
} else if (effect instanceof CombinedVibrationEffect.Sequential) {
- CombinedVibrationEffect.SequentialCombination combination =
- CombinedVibrationEffect.startSequential();
List<CombinedVibrationEffect> effects =
((CombinedVibrationEffect.Sequential) effect).getEffects();
- for (CombinedVibrationEffect e : effects) {
- combination.addNext(fixupVibrationEffect(e));
+ for (int i = 0; i < effects.size(); i++) {
+ fillVibrationFallbacks(vib, effects.get(i));
}
- return combination.combine();
}
- return effect;
}
- private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Prebaked
- && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
- if (fallback != null) {
- return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
- fallback);
+ private void fillVibrationFallbacks(Vibration vib, VibrationEffect effect) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ int segmentCount = composed.getSegments().size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if (segment instanceof PrebakedSegment) {
+ PrebakedSegment prebaked = (PrebakedSegment) segment;
+ VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
+ prebaked.getEffectId());
+ if (prebaked.shouldFallback() && fallback != null) {
+ vib.addFallback(prebaked.getEffectId(), fallback);
+ }
}
}
- return effect;
}
/**
@@ -819,7 +815,7 @@
@GuardedBy("mLock")
@Nullable
- private SparseArray<VibrationEffect.Prebaked> fixupAlwaysOnEffectsLocked(
+ private SparseArray<PrebakedSegment> fixupAlwaysOnEffectsLocked(
CombinedVibrationEffect effect) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked");
try {
@@ -833,17 +829,17 @@
// Only synced combinations can be used for always-on effects.
return null;
}
- SparseArray<VibrationEffect.Prebaked> result = new SparseArray<>();
+ SparseArray<PrebakedSegment> result = new SparseArray<>();
for (int i = 0; i < effects.size(); i++) {
- VibrationEffect prebaked = effects.valueAt(i);
- if (!(prebaked instanceof VibrationEffect.Prebaked)) {
+ PrebakedSegment prebaked = extractPrebakedSegment(effects.valueAt(i));
+ if (prebaked == null) {
Slog.e(TAG, "Only prebaked effects supported for always-on.");
return null;
}
int vibratorId = effects.keyAt(i);
VibratorController vibrator = mVibrators.get(vibratorId);
if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
- result.put(vibratorId, (VibrationEffect.Prebaked) prebaked);
+ result.put(vibratorId, prebaked);
}
}
if (result.size() == 0) {
@@ -855,6 +851,20 @@
}
}
+ @Nullable
+ private static PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.Composed) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ if (composed.getSegments().size() == 1) {
+ VibrationEffectSegment segment = composed.getSegments().get(0);
+ if (segment instanceof PrebakedSegment) {
+ return (PrebakedSegment) segment;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
* allow bypassing {@link AppOpsManager} checks.
@@ -1008,10 +1018,10 @@
public final int uid;
public final String opPkg;
public final VibrationAttributes attrs;
- public final SparseArray<VibrationEffect.Prebaked> effects;
+ public final SparseArray<PrebakedSegment> effects;
AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs,
- SparseArray<VibrationEffect.Prebaked> effects) {
+ SparseArray<PrebakedSegment> effects) {
this.alwaysOnId = alwaysOnId;
this.uid = uid;
this.opPkg = opPkg;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6072a06..a9d33dc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3724,10 +3724,15 @@
// config. (Only happens when the target window is in a different root DA)
if (target != null) {
RootDisplayArea targetRoot = target.getRootDisplayArea();
- if (targetRoot != null) {
+ if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()) {
// Reposition the IME container to the target root to get the correct bounds and
// config.
targetRoot.placeImeContainer(mImeWindowsContainer);
+ // Directly hide the IME window so it doesn't flash immediately after reparenting.
+ // InsetsController will make IME visible again before animating it.
+ if (mInputMethodWindow != null) {
+ mInputMethodWindow.hide(false /* doAnimation */, false /* requestAnim */);
+ }
}
}
// 2. Assign window layers based on the IME surface parent to make sure it is on top of the
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index b31c2e4..20216c3 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -167,6 +167,11 @@
if (aodChanged) {
// Ensure the new state takes effect.
mWindowManager.mWindowPlacerLocked.performSurfacePlacement();
+ // If the device can enter AOD and keyguard at the same time, the screen will not be
+ // turned off, so the snapshot needs to be refreshed when these states are changed.
+ if (aodShowing && keyguardShowing && keyguardChanged) {
+ mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY);
+ }
}
if (keyguardChanged) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index d81181d..20c0d41 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1573,6 +1573,10 @@
} else if (document || trIsDocument) {
// Only one of these is a document. Not the droid we're looking for.
continue;
+ } else if (multiTasksAllowed) {
+ // Neither is a document, but the new task supports multiple tasks so keep the
+ // existing task
+ continue;
}
}
return i;
diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java
index 505af05..cd20c82 100644
--- a/services/core/java/com/android/server/wm/RootDisplayArea.java
+++ b/services/core/java/com/android/server/wm/RootDisplayArea.java
@@ -79,10 +79,6 @@
*/
void placeImeContainer(DisplayArea.Tokens imeContainer) {
final RootDisplayArea previousRoot = imeContainer.getRootDisplayArea();
- if (previousRoot == this) {
- // No need to reparent if IME container is below the same root.
- return;
- }
List<Feature> features = mFeatures;
for (int i = 0; i < features.size(); i++) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 8915eba..5af44317 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -635,20 +635,7 @@
mHandler.post(() -> {
try {
synchronized (mService.mGlobalLock) {
- mTmpTasks.clear();
- mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
- // Since RecentsAnimation will handle task snapshot while switching apps
- // with the best capture timing (e.g. IME window capture), No need
- // additional task capture while task is controlled by RecentsAnimation.
- if (task.isVisible() && !task.isAnimatingByRecents()) {
- mTmpTasks.add(task);
- }
- });
- // Allow taking snapshot of home when turning screen off to reduce the delay of
- // waking from secure lock to home.
- final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY &&
- mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
- snapshotTasks(mTmpTasks, allowSnapshotHome);
+ snapshotForSleeping(displayId);
}
} finally {
listener.onScreenOff();
@@ -656,6 +643,27 @@
});
}
+ /** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */
+ void snapshotForSleeping(int displayId) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
+ mTmpTasks.clear();
+ mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
+ // Since RecentsAnimation will handle task snapshot while switching apps with the best
+ // capture timing (e.g. IME window capture), No need additional task capture while task
+ // is controlled by RecentsAnimation.
+ if (task.isVisible() && !task.isAnimatingByRecents()) {
+ mTmpTasks.add(task);
+ }
+ });
+ // Allow taking snapshot of home when turning screen off to reduce the delay of waking from
+ // secure lock to home.
+ final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY
+ && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
+ snapshotTasks(mTmpTasks, allowSnapshotHome);
+ }
+
/**
* @return The {@link Appearance} flags for the top fullscreen opaque window in the given
* {@param task}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6b1071c..c99b01f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2235,11 +2235,6 @@
win.mPendingPositionChanged = null;
}
- if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
- win.prepareDrawHandlers();
- result |= RELAYOUT_RES_BLAST_SYNC;
- }
-
int attrChanges = 0;
int flagChanges = 0;
int privateFlagChanges = 0;
@@ -2512,6 +2507,12 @@
}
win.mInRelayout = false;
+ if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
+ win.prepareDrawHandlers();
+ win.markRedrawForSyncReported();
+ result |= RELAYOUT_RES_BLAST_SYNC;
+ }
+
if (configChanged) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"relayoutWindow: postNewConfigurationToHandler");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6da350b..48d4fc5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -763,6 +763,56 @@
* into mReadyDrawHandlers. Finally once we get to finishDrawing we know everything in
* mReadyDrawHandlers corresponds to state which was observed by the client and we can
* invoke the consumers.
+ *
+ * To see in more detail that this works, we can look at it like this:
+ *
+ * The client is in one of these states:
+ *
+ * 1. Asleep
+ * 2. Traversal scheduled
+ * 3. Starting traversal
+ * 4. In relayout
+ * 5. Already drawing
+ *
+ * The property we want to implement with the draw handlers is:
+ * If WM code makes a change to client observable state (e.g. configuration),
+ * and registers a draw handler (without releasing the WM lock in between),
+ * the FIRST frame reflecting that change, will be in the Transaction passed
+ * to the draw handler.
+ *
+ * We describe the expected sequencing in each of the possible client states.
+ * We aim to "prove" that the WM can call applyWithNextDraw() with the client
+ * starting in any state, and achieve the desired result.
+ *
+ * 1. Asleep: The client will wake up in response to MSG_RESIZED, call relayout,
+ * observe the changes. Relayout will return BLAST_SYNC, and the client will
+ * send the transaction to finishDrawing. Since the client was asleep. This
+ * will be the first finishDrawing reflecting the change.
+ * 2, 3: traversal scheduled/starting traversal: These two states can be considered
+ * together. Each has two sub-states:
+ * a) Traversal will call relayout. In this case we proceed like the starting
+ * from asleep case.
+ * b) Traversal will not call relayout. In this case, the client produced
+ * frame will not include the change. Because there is no call to relayout
+ * there is no call to prepareDrawHandlers() and even if the client calls
+ * finish drawing the draw handler will not be invoked. We have to wait
+ * on the client to receive MSG_RESIZED, and will sync on the next frame
+ * 4. In relayout. In this case we are careful to prepare draw handlers and check
+ * whether to return the BLAST flag at the end of relayoutWindow. This means if you
+ * add a draw handler while the client is in relayout, BLAST_SYNC will be
+ * immediately returned, and the client will submit the frame corresponding
+ * to what returns from layout. When we prepare the draw handlers we clear the
+ * flag which would later cause us to report draw for sync. Since we reported
+ * sync through relayout (by luck the client was calling relayout perhaps)
+ * there is no need for a MSG_RESIZED.
+ * 5. Already drawing. This works much like cases 2 and 3. If there is no call to
+ * finishDrawing then of course the draw handlers will not be invoked and we just
+ * wait on the next frame for sync. If there is a call to finishDrawing,
+ * the draw handler will not have been prepared (since we did not call relayout)
+ * and we will have to wait on the next frame.
+ *
+ * By this logic we can see no matter which of the client states we are in when the
+ * draw handler is added, it will always execute on the expected frame.
*/
private final List<Consumer<SurfaceControl.Transaction>> mPendingDrawHandlers
= new ArrayList<>();
@@ -3706,7 +3756,7 @@
final int displayId = getDisplayId();
fillClientWindowFrames(mClientWindowFrames);
- mRedrawForSyncReported = true;
+ markRedrawForSyncReported();
try {
mClient.resized(mClientWindowFrames, reportDraw, mergedConfiguration, forceRelayout,
@@ -5850,7 +5900,7 @@
* "in relayout", the results may be undefined but at all other times the function
* should sort of transparently work like this:
* 1. Make changes to WM hierarchy (say change app configuration)
- * 2. Call apply with next draw.
+ * 2. Call applyWithNextDraw
* 3. After finishDrawing, our consumer will be passed the Transaction
* containing the buffer, and we can merge in additional operations.
* See {@link WindowState#mPendingDrawHandlers}
@@ -5879,16 +5929,26 @@
* See {@link WindowState#mPendingDrawHandlers}
*/
boolean executeDrawHandlers(SurfaceControl.Transaction t) {
- if (t == null) t = mTmpTransaction;
boolean hadHandlers = false;
+ boolean applyHere = false;
+ if (t == null) {
+ t = mTmpTransaction;
+ applyHere = true;
+ }
+
for (int i = 0; i < mReadyDrawHandlers.size(); i++) {
mReadyDrawHandlers.get(i).accept(t);
hadHandlers = true;
}
- mReadyDrawHandlers.clear();
- mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
- t.apply();
+ if (hadHandlers) {
+ mReadyDrawHandlers.clear();
+ mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
+ }
+
+ if (applyHere) {
+ t.apply();
+ }
return hadHandlers;
}
@@ -5906,4 +5966,8 @@
@WindowManager.LayoutParams.WindowType int getWindowType() {
return mAttrs.type;
}
+
+ void markRedrawForSyncReported() {
+ mRedrawForSyncReported = true;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0c80f86..3ef7ccd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -272,14 +272,15 @@
}
mDrawState = COMMIT_DRAW_PENDING;
layoutNeeded = true;
+ }
- if (postDrawTransaction != null) {
+ if (postDrawTransaction != null) {
+ if (mLastHidden) {
mPostDrawTransaction.merge(postDrawTransaction);
+ layoutNeeded = true;
+ } else {
+ postDrawTransaction.apply();
}
- } else if (postDrawTransaction != null) {
- // If draw state is not pending we may delay applying this transaction from the client,
- // so apply it now.
- postDrawTransaction.apply();
}
return layoutNeeded;
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index f60b354..7f8168a 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -170,13 +170,13 @@
wrapper->hal()->off();
}
-static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jint amplitude) {
+static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jfloat amplitude) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorSetAmplitude failed because native wrapper was not initialized");
return;
}
- wrapper->hal()->setAmplitude(static_cast<int32_t>(amplitude));
+ wrapper->hal()->setAmplitude(static_cast<float>(amplitude));
}
static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong ptr,
@@ -313,9 +313,9 @@
{"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
{"on", "(JJJ)V", (void*)vibratorOn},
{"off", "(J)V", (void*)vibratorOff},
- {"setAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
+ {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
{"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
- {"performComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J",
+ {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
(void*)vibratorPerformComposedEffect},
{"getSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
{"getSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
@@ -334,11 +334,10 @@
jclass listenerClass = FindClassOrDie(env, listenerClassName);
sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V");
- jclass primitiveClass =
- FindClassOrDie(env, "android/os/VibrationEffect$Composition$PrimitiveEffect");
- sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I");
- sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F");
- sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I");
+ jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment");
+ sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I");
+ sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "mScale", "F");
+ sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "mDelay", "I");
return jniRegisterNativeMethods(env,
"com/android/server/vibrator/VibratorController$NativeWrapper",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index aed13b2..56e2385 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -139,12 +139,13 @@
private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
"admin-can-grant-sensors-permissions";
- private static final String TAG_NETWORK_SLICING_ENABLED = "network-slicing-enabled";
+ private static final String TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED =
+ "enterprise-network-preference-enabled";
private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
- private static final boolean NETWORK_SLICING_ENABLED_DEFAULT = true;
+ private static final boolean ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT = true;
DeviceAdminInfo info;
@@ -284,7 +285,8 @@
public String mOrganizationId;
public String mEnrollmentSpecificId;
public boolean mAdminCanGrantSensorsPermissions;
- public boolean mNetworkSlicingEnabled = NETWORK_SLICING_ENABLED_DEFAULT;
+ public boolean mEnterpriseNetworkPreferenceEnabled =
+ ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT;
private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
@@ -555,8 +557,9 @@
}
writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
mAdminCanGrantSensorsPermissions);
- if (mNetworkSlicingEnabled != NETWORK_SLICING_ENABLED_DEFAULT) {
- writeAttributeValueToXml(out, TAG_NETWORK_SLICING_ENABLED, mNetworkSlicingEnabled);
+ if (mEnterpriseNetworkPreferenceEnabled != ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT) {
+ writeAttributeValueToXml(out, TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED,
+ mEnterpriseNetworkPreferenceEnabled);
}
if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
@@ -784,9 +787,9 @@
mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
} else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false);
- } else if (TAG_NETWORK_SLICING_ENABLED.equals(tag)) {
- mNetworkSlicingEnabled = parser.getAttributeBoolean(
- null, ATTR_VALUE, NETWORK_SLICING_ENABLED_DEFAULT);
+ } else if (TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED.equals(tag)) {
+ mEnterpriseNetworkPreferenceEnabled = parser.getAttributeBoolean(
+ null, ATTR_VALUE, ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT);
} else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false);
} else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) {
@@ -1142,8 +1145,8 @@
pw.print("mAlwaysOnVpnLockdown=");
pw.println(mAlwaysOnVpnLockdown);
- pw.print("mNetworkSlicingEnabled=");
- pw.println(mNetworkSlicingEnabled);
+ pw.print("mEnterpriseNetworkPreferenceEnabled=");
+ pw.println(mEnterpriseNetworkPreferenceEnabled);
pw.print("mCommonCriteriaMode=");
pw.println(mCommonCriteriaMode);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 283895b..09ae8fc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -102,8 +102,8 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
-// TODO (b/178655595) import static android.net.ConnectivityManager.USER_PREFERENCE_ENTERPRISE;
-// TODO (b/178655595) import static android.net.ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
@@ -3089,12 +3089,13 @@
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
- boolean enableEnterpriseNetworkSlice = true;
+ boolean enableEnterpriseNetworkPreferenceEnabled = true;
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
- enableEnterpriseNetworkSlice = owner != null ? owner.mNetworkSlicingEnabled : true;
+ enableEnterpriseNetworkPreferenceEnabled = owner != null
+ ? owner.mEnterpriseNetworkPreferenceEnabled : true;
}
- updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkSlice);
+ updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkPreferenceEnabled);
startOwnerService(userId, "start-user");
}
@@ -11423,30 +11424,32 @@
}
@Override
- public void setNetworkSlicingEnabled(boolean enabled) {
+ public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) {
if (!mHasFeature) {
return;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(isProfileOwner(caller),
- "Caller is not profile owner; only profile owner may control the network slicing");
+ "Caller is not profile owner;"
+ + " only profile owner may control the enterprise network preference");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
caller.getUserId());
- if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) {
- requiredAdmin.mNetworkSlicingEnabled = enabled;
+ if (requiredAdmin != null
+ && requiredAdmin.mEnterpriseNetworkPreferenceEnabled != enabled) {
+ requiredAdmin.mEnterpriseNetworkPreferenceEnabled = enabled;
saveSettingsLocked(caller.getUserId());
}
}
updateNetworkPreferenceForUser(caller.getUserId(), enabled);
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.SET_NETWORK_SLICING_ENABLED)
+ .createEvent(DevicePolicyEnums.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED)
.setBoolean(enabled)
.write();
}
@Override
- public boolean isNetworkSlicingEnabled(int userHandle) {
+ public boolean isEnterpriseNetworkPreferenceEnabled(int userHandle) {
if (!mHasFeature) {
return false;
}
@@ -11457,7 +11460,7 @@
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle);
if (requiredAdmin != null) {
- return requiredAdmin.mNetworkSlicingEnabled;
+ return requiredAdmin.mEnterpriseNetworkPreferenceEnabled;
} else {
return false;
}
@@ -17006,18 +17009,18 @@
}
}
- private void updateNetworkPreferenceForUser(int userId, boolean enableEnterprise) {
+ private void updateNetworkPreferenceForUser(int userId,
+ boolean enableEnterpriseNetworkPreferenceEnabled) {
if (!isManagedProfile(userId)) {
return;
}
- // TODO(b/178655595)
- // int networkPreference = enable ? ConnectivityManager.USER_PREFERENCE_ENTERPRISE :
- // ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT;
- // mInjector.binderWithCleanCallingIdentity(() ->
- // mInjector.getConnectivityManager().setNetworkPreferenceForUser(
- // UserHandle.of(userId),
- // networkPreference,
- // null /* executor */, null /* listener */));
+ int networkPreference = enableEnterpriseNetworkPreferenceEnabled
+ ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT;
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.getConnectivityManager().setProfileNetworkPreference(
+ UserHandle.of(userId),
+ networkPreference,
+ null /* executor */, null /* listener */));
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
index fa6ef00..5f35a26 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
@@ -149,7 +149,8 @@
.setLocalOnly(true)
.setContentIntent(pendingDialogIntent)
.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
+ com.android.internal.R.color.system_notification_accent_color))
+ .extend(new Notification.TvExtender());
if (type == NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
builder.setContentTitle(mContext.getString(
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 932e997..bd3f99a 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -81,8 +81,8 @@
static constexpr auto bindingTimeout = 1min;
- // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
- static constexpr auto minBindDelay = 10s;
+ // 1s, 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
+ static constexpr auto minBindDelay = 1s;
static constexpr auto maxBindDelay = 10000s;
static constexpr auto bindDelayMultiplier = 10;
static constexpr auto bindDelayJitterDivider = 10;
diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp
index 338659d..bf4e9616 100644
--- a/services/incremental/path.cpp
+++ b/services/incremental/path.cpp
@@ -44,19 +44,20 @@
PathCharsLess());
}
-static void preparePathComponent(std::string_view& path, bool trimFront) {
- if (trimFront) {
- while (!path.empty() && path.front() == '/') {
- path.remove_prefix(1);
- }
+static void preparePathComponent(std::string_view& path, bool trimAll) {
+ // need to check for double front slash as a single one has a separate meaning in front
+ while (!path.empty() && path.front() == '/' &&
+ (trimAll || (path.size() > 1 && path[1] == '/'))) {
+ path.remove_prefix(1);
}
- while (!path.empty() && path.back() == '/') {
+ // for the back we don't care about double-vs-single slash difference
+ while (path.size() > !trimAll && path.back() == '/') {
path.remove_suffix(1);
}
}
void details::append_next_path(std::string& target, std::string_view path) {
- preparePathComponent(path, true);
+ preparePathComponent(path, !target.empty());
if (path.empty()) {
return;
}
diff --git a/services/incremental/path.h b/services/incremental/path.h
index 3e5fd21..e12e1d0 100644
--- a/services/incremental/path.h
+++ b/services/incremental/path.h
@@ -89,15 +89,25 @@
bool startsWith(std::string_view path, std::string_view prefix);
template <class... Paths>
-std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
- std::string result;
+std::string join(std::string&& first, std::string_view second, Paths&&... paths) {
+ std::string& result = first;
{
using std::size;
result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths)));
}
- result.assign(first);
- (details::append_next_path(result, second), ..., details::append_next_path(result, paths));
- return result;
+ (details::append_next_path(result, second), ...,
+ details::append_next_path(result, std::forward<Paths>(paths)));
+ return std::move(result);
+}
+
+template <class... Paths>
+std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
+ return path::join(std::string(), first, second, std::forward<Paths>(paths)...);
+}
+
+template <class... Paths>
+std::string join(const char* first, std::string_view second, Paths&&... paths) {
+ return path::join(std::string_view(first), second, std::forward<Paths>(paths)...);
}
} // namespace android::incremental::path
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index de8822d..e6d4872 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -272,12 +272,20 @@
}
return binder::Status::ok();
}
+ binder::Status bindToDataLoaderOkWith1sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(100 * 9 <= bindDelayMs && bindDelayMs <= 100 * 11) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId,
const DataLoaderParamsParcel& params,
int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return) {
- CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs;
+ CHECK(100 * 9 * 9 <= bindDelayMs && bindDelayMs <= 100 * 11 * 11) << bindDelayMs;
return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
}
binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId,
@@ -285,7 +293,7 @@
int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return) {
- CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs;
+ CHECK(100 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 100 * 11 * 11 * 11) << bindDelayMs;
return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
}
binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId,
@@ -293,7 +301,8 @@
int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return) {
- CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs;
+ CHECK(100 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 100 * 11 * 11 * 11 * 11)
+ << bindDelayMs;
return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
}
binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId,
@@ -301,7 +310,7 @@
int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return) {
- CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11)
+ CHECK(100 * 9 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 100 * 11 * 11 * 11 * 11 * 11)
<< bindDelayMs;
return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
}
@@ -865,10 +874,10 @@
}
TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) {
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(7);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
- EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
- EXPECT_CALL(*mDataLoader, start(_)).Times(6);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(7);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(7);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
@@ -882,6 +891,11 @@
ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
.WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith1sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
&MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
mDataLoaderManager->setDataLoaderStatusDestroyed();
@@ -912,10 +926,10 @@
constexpr auto bindRetryInterval = 5s;
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(10);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(11);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
- EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
- EXPECT_CALL(*mDataLoader, start(_)).Times(6);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(7);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(7);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
@@ -995,6 +1009,11 @@
// Simulated crash/other connection breakage.
ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
.WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith1sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
&MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
mDataLoaderManager->setDataLoaderStatusDestroyed();
diff --git a/services/incremental/test/path_test.cpp b/services/incremental/test/path_test.cpp
index cbe479e1..4a8ae5b 100644
--- a/services/incremental/test/path_test.cpp
+++ b/services/incremental/test/path_test.cpp
@@ -40,4 +40,24 @@
EXPECT_TRUE(!PathLess()("/a/b", "/a"));
}
+TEST(Path, Join) {
+ EXPECT_STREQ("", path::join("", "").c_str());
+
+ EXPECT_STREQ("/", path::join("", "/").c_str());
+ EXPECT_STREQ("/", path::join("/", "").c_str());
+ EXPECT_STREQ("/", path::join("/", "/").c_str());
+ EXPECT_STREQ("/", path::join("/"s, "/").c_str());
+ EXPECT_STREQ("/", path::join("/"sv, "/").c_str());
+ EXPECT_STREQ("/", path::join("/", "/", "/", "/", "/", "/", "/", "/", "/", "/").c_str());
+
+ EXPECT_STREQ("/a/b/c/d", path::join("/a/b/"s, "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/a/b/", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b/", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/", "//a/b//", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("", "", "/", "//a/b//", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join(""s, "", "/", "//a/b//", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/a/b", "", "", "/", "", "/", "/", "/c", "d").c_str());
+}
+
} // namespace android::incremental::path
diff --git a/services/net/Android.bp b/services/net/Android.bp
index b01e425..800f7ad 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -83,3 +83,15 @@
"//packages/modules/Connectivity/Tethering"
],
}
+
+filegroup {
+ name: "services-connectivity-shared-srcs",
+ srcs: [
+ // TODO: move to networkstack-client
+ "java/android/net/IpMemoryStore.java",
+ "java/android/net/NetworkMonitorManager.java",
+ // TODO: move to libs/net
+ "java/android/net/util/KeepalivePacketDataUtil.java",
+ "java/android/net/util/NetworkConstants.java",
+ ],
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
index 8ae4c5a..14c02d5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
import com.android.server.pm.verify.domain.DomainVerificationService;
@@ -45,4 +46,16 @@
throws PackageManager.NameNotFoundException {
return service.setDomainVerificationUserSelection(domainSetId, domains, enabled, userId);
}
+
+ static int setStatusForceNullable(@NonNull DomainVerificationManager manager,
+ @Nullable UUID domainSetId, @Nullable Set<String> domains, int state)
+ throws PackageManager.NameNotFoundException {
+ return manager.setDomainVerificationStatus(domainSetId, domains, state);
+ }
+
+ static int setUserSelectionForceNullable(@NonNull DomainVerificationManager manager,
+ @Nullable UUID domainSetId, @Nullable Set<String> domains, boolean enabled)
+ throws PackageManager.NameNotFoundException {
+ return manager.setDomainVerificationUserSelection(domainSetId, domains, enabled);
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index ef79b08..881604f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -16,6 +16,7 @@
package com.android.server.pm.test.verify.domain
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.parsing.component.ParsedActivity
@@ -23,6 +24,7 @@
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState
+import android.content.pm.verify.domain.IDomainVerificationManager
import android.os.Build
import android.os.PatternMatcher
import android.os.Process
@@ -127,15 +129,17 @@
assertThat(service.setStatus(UUID_INVALID, setOf(DOMAIN_1), 1100))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
- assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, null,
- setOf(DOMAIN_1), 1100))
- .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL)
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setStatusForceNullable(service, null, setOf(DOMAIN_1), 1100)
+ }
- assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null,
- 1100)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null, 1100)
+ }
- assertThat(service.setStatus(UUID_ONE, emptySet(), 1100))
- .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+ assertFailsWith(IllegalArgumentException::class) {
+ service.setStatus(UUID_ONE, emptySet(), 1100)
+ }
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100))
.isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
@@ -143,8 +147,9 @@
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), 1100))
.isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
- assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15))
- .isEqualTo(DomainVerificationManager.ERROR_INVALID_STATE_CODE)
+ assertFailsWith(IllegalArgumentException::class) {
+ service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15)
+ }
map.clear()
assertFailsWith(PackageManager.NameNotFoundException::class){
@@ -198,15 +203,19 @@
assertThat(service.setUserSelection(UUID_INVALID, setOf(DOMAIN_1), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
- assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null,
- setOf(DOMAIN_1), true, 0))
- .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL)
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null,
+ setOf(DOMAIN_1), true, 0)
+ }
- assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null,
- true, 0)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null,
+ true, 0)
+ }
- assertThat(service.setUserSelection(UUID_ONE, emptySet(), true, 0))
- .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+ assertFailsWith(IllegalArgumentException::class) {
+ service.setUserSelection(UUID_ONE, emptySet(), true, 0)
+ }
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
@@ -297,6 +306,48 @@
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
}
+ @Test
+ fun appProcessManager() {
+ // The app side DomainVerificationManager also has to do some argument enforcement since
+ // the input values are transformed before they are sent across Binder. Verify that here.
+
+ // Mock nothing to ensure no calls are made before failing
+ val context = mockThrowOnUnmocked<Context>()
+ val binderInterface = mockThrowOnUnmocked<IDomainVerificationManager>()
+
+ val manager = DomainVerificationManager(context, binderInterface)
+
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setStatusForceNullable(manager, null, setOf(DOMAIN_1), 1100)
+ }
+
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setStatusForceNullable(manager, UUID_ONE, null, 1100)
+ }
+
+ assertFailsWith(IllegalArgumentException::class) {
+ manager.setDomainVerificationStatus(UUID_ONE, emptySet(), 1100)
+ }
+
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setUserSelectionForceNullable(
+ manager, null,
+ setOf(DOMAIN_1), true
+ )
+ }
+
+ assertFailsWith(IllegalArgumentException::class) {
+ DomainVerificationJavaUtil.setUserSelectionForceNullable(
+ manager, UUID_ONE,
+ null, true
+ )
+ }
+
+ assertFailsWith(IllegalArgumentException::class) {
+ manager.setDomainVerificationUserSelection(UUID_ONE, emptySet(), true)
+ }
+ }
+
private fun makeService(vararg pkgSettings: PackageSetting) =
makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index ee1a4f4..4bab8e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -5416,9 +5416,8 @@
inOrder.verify(mJobSchedulerService,
timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
.onControllerStateChanged();
- // Top and bg EJs should still be allowed to run since they started before the app ran
- // out of quota.
- assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
+ // Top should still be "in quota" since it started before the app ran on top out of quota.
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
assertFalse(
jobUnstarted.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
@@ -5467,7 +5466,7 @@
// wasn't started. Top should remain in quota since it started when the app was in TOP.
assertTrue(jobTop2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
- assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
trackJobs(jobBg2);
assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
assertFalse(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 17c6b6f..db0c3ae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -36,6 +36,7 @@
import android.os.UserHandle
import android.os.UserManager
import android.os.incremental.IncrementalManager
+import android.provider.DeviceConfig
import android.util.ArrayMap
import android.util.DisplayMetrics
import android.util.EventLog
@@ -131,6 +132,7 @@
.mockStatic(LockGuard::class.java)
.mockStatic(EventLog::class.java)
.mockStatic(LocalServices::class.java)
+ .mockStatic(DeviceConfig::class.java)
.apply(withSession)
session = apply.startMocking()
whenever(mocks.settings.insertPackageSettingLPw(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index a0e208f..46487ea2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -17,8 +17,13 @@
package com.android.server.pm
import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
import com.android.server.apphibernation.AppHibernationManagerInternal
+import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.testutils.whenever
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -33,7 +38,10 @@
companion object {
val TEST_PACKAGE_NAME = "test.package"
+ val TEST_PACKAGE_2_NAME = "test.package2"
val TEST_USER_ID = 0
+
+ val KEY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"
}
@Rule
@@ -47,6 +55,8 @@
@Throws(Exception::class)
fun setup() {
MockitoAnnotations.initMocks(this)
+ wheneverStatic { DeviceConfig.getBoolean(
+ NAMESPACE_APP_HIBERNATION, KEY_APP_HIBERNATION_ENABLED, false) }.thenReturn(true)
rule.system().stageNominalSystemState()
whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java))
.thenReturn(appHibernationManager)
@@ -68,6 +78,28 @@
verify(appHibernationManager).setHibernatingGlobally(TEST_PACKAGE_NAME, false)
}
+ @Test
+ fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() {
+ rule.system().stageScanExistingPackage(
+ TEST_PACKAGE_NAME,
+ 1L,
+ rule.system().dataAppDirectory,
+ withPackage = { it.apply { isHasCode = true } })
+ rule.system().stageScanExistingPackage(
+ TEST_PACKAGE_2_NAME,
+ 1L,
+ rule.system().dataAppDirectory,
+ withPackage = { it.apply { isHasCode = true } })
+ val pm = createPackageManagerService()
+ rule.system().validateFinalState()
+ whenever(appHibernationManager.isHibernatingGlobally(TEST_PACKAGE_2_NAME)).thenReturn(true)
+
+ val optimizablePkgs = pm.optimizablePackages
+
+ assertTrue(optimizablePkgs.contains(TEST_PACKAGE_NAME))
+ assertFalse(optimizablePkgs.contains(TEST_PACKAGE_2_NAME))
+ }
+
private fun createPackageManagerService(): PackageManagerService {
return PackageManagerService(rule.mocks().injector,
false /*coreOnly*/,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
new file mode 100644
index 0000000..7a6110b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 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.pm
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.util.SparseArray
+import com.android.server.testutils.any
+import com.android.server.testutils.eq
+import com.android.server.testutils.nullable
+import com.android.server.testutils.whenever
+import com.android.server.utils.WatchedArrayMap
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+class SuspendPackagesBroadcastTest {
+
+ companion object {
+ const val TEST_PACKAGE_1 = "com.android.test.package1"
+ const val TEST_PACKAGE_2 = "com.android.test.package2"
+ const val TEST_USER_ID = 0
+ }
+
+ lateinit var pms: PackageManagerService
+ lateinit var packageSetting1: PackageSetting
+ lateinit var packageSetting2: PackageSetting
+ lateinit var packagesToSuspend: Array<String>
+ lateinit var uidsToSuspend: IntArray
+
+ @Captor
+ lateinit var bundleCaptor: ArgumentCaptor<Bundle>
+
+ @Rule
+ @JvmField
+ val rule = MockSystemRule()
+
+ @Before
+ @Throws(Exception::class)
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ rule.system().stageNominalSystemState()
+ pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2))
+ packageSetting1 = pms.getPackageSetting(TEST_PACKAGE_1)!!
+ packageSetting2 = pms.getPackageSetting(TEST_PACKAGE_2)!!
+ packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
+ mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+ mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
+
+ pms.sendPackagesSuspendedForUser(
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
+
+ var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(changedUids).asList().containsExactly(
+ packageSetting1.appId, packageSetting2.appId)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
+ mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+ mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
+
+ pms.sendPackagesSuspendedForUser(
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
+
+ bundleCaptor.allValues.forEach {
+ var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(changedPackages?.size).isEqualTo(1)
+ assertThat(changedUids?.size).isEqualTo(1)
+ assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+ }
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
+ mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+ mockAllowList(packageSetting2, null)
+
+ pms.sendPackagesSuspendedForUser(
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+ bundleCaptor.allValues.forEach {
+ var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(changedPackages?.size).isEqualTo(1)
+ assertThat(changedUids?.size).isEqualTo(1)
+ assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+ }
+ }
+
+ private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
+ this.put(TEST_USER_ID, uids)
+ }
+
+ private fun mockAllowList(pkgSetting: PackageSetting, list: SparseArray<IntArray>?) {
+ whenever(rule.mocks().injector.appsFilter.getVisibilityAllowList(eq(pkgSetting),
+ any(IntArray::class.java), any() as WatchedArrayMap<String, PackageSetting>))
+ .thenReturn(list)
+ }
+
+ private fun createPackageManagerService(vararg stageExistingPackages: String):
+ PackageManagerService {
+ stageExistingPackages.forEach {
+ rule.system().stageScanExistingPackage(it, 1L,
+ rule.system().dataAppDirectory)
+ }
+ var pms = PackageManagerService(rule.mocks().injector,
+ false /*coreOnly*/,
+ false /*factoryTest*/,
+ MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+ false /*isEngBuild*/,
+ false /*isUserDebugBuild*/,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL)
+ rule.system().validateFinalState()
+ return pms
+ }
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index b9aa554..5761958 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -74,6 +74,9 @@
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="android.permission.DUMP"/>
<uses-permission android:name="android.permission.READ_DREAM_STATE"/>
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
index 3d0895d..24a8b61 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -49,14 +49,18 @@
private static final int USER_ID_2 = 1002;
// Stolen from ConnectivityServiceTest.MockContext
- class MockContext extends ContextWrapper {
+ static class MockContext extends ContextWrapper {
private static final String TAG = "MockContext";
// Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
+ @Mock
+ private final MockPackageManager mMockPackageManager;
+
MockContext(Context base) {
super(base);
+ mMockPackageManager = new MockPackageManager();
}
/**
@@ -101,6 +105,11 @@
throw new SecurityException("[Test] permission denied: " + permission);
}
}
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
}
@Mock
diff --git a/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java b/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java
new file mode 100644
index 0000000..5edbc16
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java
@@ -0,0 +1,1133 @@
+/*
+ * Copyright (C) 2021 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.app;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+public class MockPackageManager extends PackageManager {
+ private static final String TAG = "MockPackageManager";
+
+ private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
+
+ public MockPackageManager() {
+ // Mock the ApplicationInfo, so we can treat the test as a "game".
+ mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ }
+
+ @Override
+ public PackageInfo getPackageInfo(@NonNull String packageName, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public PackageInfo getPackageInfoAsUser(@NonNull String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public String[] currentToCanonicalPackageNames(@NonNull String[] packageNames) {
+ return new String[0];
+ }
+
+ @Override
+ public String[] canonicalToCurrentPackageNames(@NonNull String[] packageNames) {
+ return new String[0];
+ }
+
+ @Nullable
+ @Override
+ public Intent getLaunchIntentForPackage(@NonNull String packageName) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Intent getCarLaunchIntentForPackage(@NonNull String packageName) {
+ return null;
+ }
+
+ @Override
+ public int[] getPackageGids(@NonNull String packageName) throws NameNotFoundException {
+ return new int[0];
+ }
+
+ @Override
+ public int[] getPackageGids(@NonNull String packageName, int flags)
+ throws NameNotFoundException {
+ return new int[0];
+ }
+
+ @Override
+ public int getPackageUid(@NonNull String packageName, int flags)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int getPackageUidAsUser(@NonNull String packageName, int userId)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int getPackageUidAsUser(@NonNull String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public PermissionInfo getPermissionInfo(@NonNull String permName, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<PermissionInfo> queryPermissionsByGroup(@NonNull String permissionGroup,
+ int flags) throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public boolean arePermissionsIndividuallyControlled() {
+ return false;
+ }
+
+ @Override
+ public boolean isWirelessConsentModeEnabled() {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public PermissionGroupInfo getPermissionGroupInfo(@NonNull String groupName, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ApplicationInfo getApplicationInfo(@NonNull String packageName, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, int flags,
+ int userId) throws NameNotFoundException {
+ return mApplicationInfo;
+ }
+
+ @NonNull
+ @Override
+ public ActivityInfo getActivityInfo(@NonNull ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ServiceInfo getServiceInfo(@NonNull ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<PackageInfo> getInstalledPackages(int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<PackageInfo> getPackagesHoldingPermissions(@NonNull String[] permissions,
+ int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public int checkPermission(@NonNull String permName, @NonNull String packageName) {
+ return 0;
+ }
+
+ @Override
+ public boolean isPermissionRevokedByPolicy(@NonNull String permName,
+ @NonNull String packageName) {
+ return false;
+ }
+
+ @Override
+ public boolean addPermission(@NonNull PermissionInfo info) {
+ return false;
+ }
+
+ @Override
+ public boolean addPermissionAsync(@NonNull PermissionInfo info) {
+ return false;
+ }
+
+ @Override
+ public void removePermission(@NonNull String permName) {
+
+ }
+
+ @Override
+ public void grantRuntimePermission(@NonNull String packageName, @NonNull String permName,
+ @NonNull UserHandle user) {
+
+ }
+
+ @Override
+ public void revokeRuntimePermission(@NonNull String packageName, @NonNull String permName,
+ @NonNull UserHandle user) {
+
+ }
+
+ @Override
+ public int getPermissionFlags(@NonNull String permName, @NonNull String packageName,
+ @NonNull UserHandle user) {
+ return 0;
+ }
+
+ @Override
+ public void updatePermissionFlags(@NonNull String permName, @NonNull String packageName,
+ int flagMask, int flagValues, @NonNull UserHandle user) {
+
+ }
+
+ @Override
+ public boolean shouldShowRequestPermissionRationale(@NonNull String permName) {
+ return false;
+ }
+
+ @Override
+ public int checkSignatures(@NonNull String packageName1, @NonNull String packageName2) {
+ return 0;
+ }
+
+ @Override
+ public int checkSignatures(int uid1, int uid2) {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public String[] getPackagesForUid(int uid) {
+ return new String[0];
+ }
+
+ @Nullable
+ @Override
+ public String getNameForUid(int uid) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String[] getNamesForUids(int[] uids) {
+ return new String[0];
+ }
+
+ @Override
+ public int getUidForSharedUser(@NonNull String sharedUserName)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public List<ApplicationInfo> getInstalledApplications(int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<InstantAppInfo> getInstantApps() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getInstantAppIcon(String packageName) {
+ return null;
+ }
+
+ @Override
+ public boolean isInstantApp() {
+ return false;
+ }
+
+ @Override
+ public boolean isInstantApp(@NonNull String packageName) {
+ return false;
+ }
+
+ @Override
+ public int getInstantAppCookieMaxBytes() {
+ return 0;
+ }
+
+ @Override
+ public int getInstantAppCookieMaxSize() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public byte[] getInstantAppCookie() {
+ return new byte[0];
+ }
+
+ @Override
+ public void clearInstantAppCookie() {
+
+ }
+
+ @Override
+ public void updateInstantAppCookie(@Nullable byte[] cookie) {
+
+ }
+
+ @Override
+ public boolean setInstantAppCookie(@Nullable byte[] cookie) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public String[] getSystemSharedLibraryNames() {
+ return new String[0];
+ }
+
+ @NonNull
+ @Override
+ public List<SharedLibraryInfo> getSharedLibraries(int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public String getServicesSystemSharedLibraryPackageName() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public String getSharedSystemSharedLibraryPackageName() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ChangedPackages getChangedPackages(int sequenceNumber) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public FeatureInfo[] getSystemAvailableFeatures() {
+ return new FeatureInfo[0];
+ }
+
+ @Override
+ public boolean hasSystemFeature(@NonNull String featureName) {
+ return false;
+ }
+
+ @Override
+ public boolean hasSystemFeature(@NonNull String featureName, int version) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public ResolveInfo resolveActivity(@NonNull Intent intent, int flags) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ResolveInfo resolveActivityAsUser(@NonNull Intent intent, int flags, int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent, int flags,
+ int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
+ @Nullable Intent[] specifics, @NonNull Intent intent, int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent, int flags,
+ int userId) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ResolveInfo resolveService(@NonNull Intent intent, int flags) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ResolveInfo resolveServiceAsUser(@NonNull Intent intent, int flags, int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryIntentServices(@NonNull Intent intent, int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, int flags,
+ int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent,
+ int flags, int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent, int flags) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ProviderInfo resolveContentProvider(@NonNull String authority, int flags) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ProviderInfo resolveContentProviderAsUser(@NonNull String providerName, int flags,
+ int userId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<ProviderInfo> queryContentProviders(@Nullable String processName, int uid,
+ int flags) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName className,
+ int flags) throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<InstrumentationInfo> queryInstrumentation(@NonNull String targetPackage,
+ int flags) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getDrawable(@NonNull String packageName, int resid,
+ @Nullable ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable getActivityIcon(@NonNull ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable getActivityIcon(@NonNull Intent intent) throws NameNotFoundException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getActivityBanner(@NonNull ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getActivityBanner(@NonNull Intent intent) throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable getDefaultActivityIcon() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable getApplicationIcon(@NonNull ApplicationInfo info) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable getApplicationIcon(@NonNull String packageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getApplicationBanner(@NonNull ApplicationInfo info) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getApplicationBanner(@NonNull String packageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getActivityLogo(@NonNull ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getActivityLogo(@NonNull Intent intent) throws NameNotFoundException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getApplicationLogo(@NonNull ApplicationInfo info) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getApplicationLogo(@NonNull String packageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable getUserBadgedIcon(@NonNull Drawable drawable, @NonNull UserHandle user) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable getUserBadgedDrawableForDensity(@NonNull Drawable drawable,
+ @NonNull UserHandle user, @Nullable Rect badgeLocation, int badgeDensity) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getUserBadgeForDensity(@NonNull UserHandle user, int density) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getUserBadgeForDensityNoBackground(@NonNull UserHandle user, int density) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public CharSequence getUserBadgedLabel(@NonNull CharSequence label,
+ @NonNull UserHandle user) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getText(@NonNull String packageName, int resid,
+ @Nullable ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public XmlResourceParser getXml(@NonNull String packageName, int resid,
+ @Nullable ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public CharSequence getApplicationLabel(@NonNull ApplicationInfo info) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Resources getResourcesForActivity(@NonNull ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Resources getResourcesForApplication(@NonNull String packageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Resources getResourcesForApplicationAsUser(@NonNull String packageName, int userId)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public int installExistingPackage(@NonNull String packageName)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int installExistingPackage(@NonNull String packageName, int installReason)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int installExistingPackageAsUser(@NonNull String packageName, int userId)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public void verifyPendingInstall(int id, int verificationCode) {
+
+ }
+
+ @Override
+ public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+ long millisecondsToDelay) {
+
+ }
+
+ @Override
+ public void verifyIntentFilter(int verificationId, int verificationCode,
+ @NonNull List<String> failedDomains) {
+
+ }
+
+ @Override
+ public int getIntentVerificationStatusAsUser(@NonNull String packageName, int userId) {
+ return 0;
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatusAsUser(@NonNull String packageName, int status,
+ int userId) {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public List<IntentFilterVerificationInfo> getIntentFilterVerifications(
+ @NonNull String packageName) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<IntentFilter> getAllIntentFilters(@NonNull String packageName) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getDefaultBrowserPackageNameAsUser(int userId) {
+ return null;
+ }
+
+ @Override
+ public boolean setDefaultBrowserPackageNameAsUser(@Nullable String packageName,
+ int userId) {
+ return false;
+ }
+
+ @Override
+ public void setInstallerPackageName(@NonNull String targetPackage,
+ @Nullable String installerPackageName) {
+
+ }
+
+ @Override
+ public void setUpdateAvailable(@NonNull String packageName, boolean updateAvaialble) {
+
+ }
+
+ @Override
+ public void deletePackage(@NonNull String packageName,
+ @Nullable IPackageDeleteObserver observer, int flags) {
+
+ }
+
+ @Override
+ public void deletePackageAsUser(@NonNull String packageName,
+ @Nullable IPackageDeleteObserver observer, int flags, int userId) {
+
+ }
+
+ @Nullable
+ @Override
+ public String getInstallerPackageName(@NonNull String packageName) {
+ return null;
+ }
+
+ @Override
+ public void clearApplicationUserData(@NonNull String packageName,
+ @Nullable IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFiles(@NonNull String packageName,
+ @Nullable IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFilesAsUser(@NonNull String packageName, int userId,
+ @Nullable IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void freeStorageAndNotify(@Nullable String volumeUuid, long freeStorageSize,
+ @Nullable IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void freeStorage(@Nullable String volumeUuid, long freeStorageSize,
+ @Nullable IntentSender pi) {
+
+ }
+
+ @Override
+ public void getPackageSizeInfoAsUser(@NonNull String packageName, int userId,
+ @Nullable IPackageStatsObserver observer) {
+
+ }
+
+ @Override
+ public void addPackageToPreferred(@NonNull String packageName) {
+
+ }
+
+ @Override
+ public void removePackageFromPreferred(@NonNull String packageName) {
+
+ }
+
+ @NonNull
+ @Override
+ public List<PackageInfo> getPreferredPackages(int flags) {
+ return null;
+ }
+
+ @Override
+ public void addPreferredActivity(@NonNull IntentFilter filter, int match,
+ @Nullable ComponentName[] set, @NonNull ComponentName activity) {
+
+ }
+
+ @Override
+ public void replacePreferredActivity(@NonNull IntentFilter filter, int match,
+ @Nullable ComponentName[] set, @NonNull ComponentName activity) {
+
+ }
+
+ @Override
+ public void clearPackagePreferredActivities(@NonNull String packageName) {
+
+ }
+
+ @Override
+ public int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
+ @NonNull List<ComponentName> outActivities, @Nullable String packageName) {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public ComponentName getHomeActivities(@NonNull List<ResolveInfo> outActivities) {
+ return null;
+ }
+
+ @Override
+ public void setComponentEnabledSetting(@NonNull ComponentName componentName, int newState,
+ int flags) {
+
+ }
+
+ @Override
+ public int getComponentEnabledSetting(@NonNull ComponentName componentName) {
+ return 0;
+ }
+
+ @Override
+ public void setApplicationEnabledSetting(@NonNull String packageName, int newState,
+ int flags) {
+
+ }
+
+ @Override
+ public int getApplicationEnabledSetting(@NonNull String packageName) {
+ return 0;
+ }
+
+ @Override
+ public void flushPackageRestrictionsAsUser(int userId) {
+
+ }
+
+ @Override
+ public boolean setApplicationHiddenSettingAsUser(@NonNull String packageName,
+ boolean hidden, @NonNull UserHandle userHandle) {
+ return false;
+ }
+
+ @Override
+ public boolean getApplicationHiddenSettingAsUser(@NonNull String packageName,
+ @NonNull UserHandle userHandle) {
+ return false;
+ }
+
+ @Override
+ public boolean isSafeMode() {
+ return false;
+ }
+
+ @Override
+ public void addOnPermissionsChangeListener(@NonNull OnPermissionsChangedListener listener) {
+
+ }
+
+ @Override
+ public void removeOnPermissionsChangeListener(
+ @NonNull OnPermissionsChangedListener listener) {
+
+ }
+
+ @NonNull
+ @Override
+ public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public KeySet getSigningKeySet(@NonNull String packageName) {
+ return null;
+ }
+
+ @Override
+ public boolean isSignedBy(@NonNull String packageName, @NonNull KeySet ks) {
+ return false;
+ }
+
+ @Override
+ public boolean isSignedByExactly(@NonNull String packageName, @NonNull KeySet ks) {
+ return false;
+ }
+
+ @Override
+ public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
+ return false;
+ }
+
+ @Override
+ public void setApplicationCategoryHint(@NonNull String packageName, int categoryHint) {
+
+ }
+
+ @Override
+ public int getMoveStatus(int moveId) {
+ return 0;
+ }
+
+ @Override
+ public void registerMoveCallback(@NonNull MoveCallback callback, @NonNull Handler handler) {
+
+ }
+
+ @Override
+ public void unregisterMoveCallback(@NonNull MoveCallback callback) {
+
+ }
+
+ @Override
+ public int movePackage(@NonNull String packageName, @NonNull VolumeInfo vol) {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public VolumeInfo getPackageCurrentVolume(@NonNull ApplicationInfo app) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<VolumeInfo> getPackageCandidateVolumes(@NonNull ApplicationInfo app) {
+ return null;
+ }
+
+ @Override
+ public int movePrimaryStorage(@NonNull VolumeInfo vol) {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public VolumeInfo getPrimaryStorageCurrentVolume() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public VerifierDeviceIdentity getVerifierDeviceIdentity() {
+ return null;
+ }
+
+ @Override
+ public boolean isUpgrade() {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public PackageInstaller getPackageInstaller() {
+ return null;
+ }
+
+ @Override
+ public void addCrossProfileIntentFilter(@NonNull IntentFilter filter, int sourceUserId,
+ int targetUserId, int flags) {
+
+ }
+
+ @Override
+ public void clearCrossProfileIntentFilters(int sourceUserId) {
+
+ }
+
+ @NonNull
+ @Override
+ public Drawable loadItemIcon(@NonNull PackageItemInfo itemInfo,
+ @Nullable ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo,
+ @Nullable ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageAvailable(@NonNull String packageName) {
+ return false;
+ }
+
+ @Override
+ public int getInstallReason(@NonNull String packageName, @NonNull UserHandle user) {
+ return 0;
+ }
+
+ @Override
+ public boolean canRequestPackageInstalls() {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ComponentName getInstantAppInstallerComponent() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getInstantAppAndroidId(@NonNull String packageName,
+ @NonNull UserHandle user) {
+ return null;
+ }
+
+ @Override
+ public void registerDexModule(@NonNull String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback) {
+
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 1b8ab21..0dbf3fe 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.intThat;
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 android.app.IActivityManager;
@@ -36,6 +37,7 @@
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.net.Uri;
@@ -53,11 +55,11 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Tests for {@link com.android.server.apphibernation.AppHibernationService}
@@ -81,6 +83,8 @@
@Mock
private IPackageManager mIPackageManager;
@Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
private IActivityManager mIActivityManager;
@Mock
private UserManager mUserManager;
@@ -116,8 +120,8 @@
mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
UserInfo userInfo = addUser(USER_ID_1);
- mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1);
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
mAppHibernationService.mIsServiceEnabled = true;
}
@@ -150,8 +154,8 @@
throws RemoteException {
// WHEN a new user is added and a package from the user is hibernated
UserInfo user2 = addUser(USER_ID_2);
- mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
// THEN the new user's package is hibernated
@@ -188,8 +192,8 @@
// GIVEN an unlocked user with all packages installed
UserInfo userInfo =
addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
- mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
// WHEN packages are hibernated for the user
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
@@ -254,18 +258,29 @@
}
@Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
+ @Override
public UserManager getUserManager() {
return mUserManager;
}
@Override
+ public Executor getBackgroundExecutor() {
+ // Just execute immediately in tests.
+ return r -> r.run();
+ }
+
+ @Override
public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
- return Mockito.mock(HibernationStateDiskStore.class);
+ return mock(HibernationStateDiskStore.class);
}
@Override
public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
- return Mockito.mock(HibernationStateDiskStore.class);
+ return mock(HibernationStateDiskStore.class);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
new file mode 100644
index 0000000..557c14a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class UserAwareBiometricSchedulerTest {
+
+ private static final String TAG = "BiometricSchedulerTest";
+ private static final int TEST_SENSOR_ID = 0;
+
+ private UserAwareBiometricScheduler mScheduler;
+ private IBinder mToken;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private IBiometricService mBiometricService;
+
+ private TestUserStartedCallback mUserStartedCallback;
+ private TestUserStoppedCallback mUserStoppedCallback;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mToken = new Binder();
+ mUserStartedCallback = new TestUserStartedCallback();
+ mUserStoppedCallback = new TestUserStoppedCallback();
+
+ mScheduler = new UserAwareBiometricScheduler(TAG,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ () -> mCurrentUserId,
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new TestStopUserClient(mContext, Object::new, mToken, userId,
+ TEST_SENSOR_ID, mUserStoppedCallback);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
+ TEST_SENSOR_ID, mUserStartedCallback);
+ }
+ });
+ }
+
+ @Test
+ public void testScheduleOperation_whenNoUser() {
+ mCurrentUserId = UserHandle.USER_NULL;
+
+ final int nextUserId = 0;
+
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+ verify(nextClient, never()).start(any());
+ assertEquals(0, mUserStoppedCallback.numInvocations);
+ assertEquals(1, mUserStartedCallback.numInvocations);
+
+ waitForIdle();
+ verify(nextClient).start(any());
+ }
+
+ @Test
+ public void testScheduleOperation_whenSameUser() {
+ mCurrentUserId = 10;
+
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+
+ waitForIdle();
+
+ verify(nextClient).start(any());
+ assertEquals(0, mUserStoppedCallback.numInvocations);
+ assertEquals(0, mUserStartedCallback.numInvocations);
+ }
+
+ @Test
+ public void testScheduleOperation_whenDifferentUser() {
+ mCurrentUserId = 10;
+
+ final int nextUserId = 11;
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+
+ waitForIdle();
+ assertEquals(1, mUserStoppedCallback.numInvocations);
+
+ waitForIdle();
+ assertEquals(1, mUserStartedCallback.numInvocations);
+
+ waitForIdle();
+ verify(nextClient).start(any());
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
+
+ int numInvocations;
+
+ @Override
+ public void onUserStopped() {
+ numInvocations++;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ }
+
+ private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> {
+
+ int numInvocations;
+
+ @Override
+ public void onUserStarted(int newUserId, Object newObject) {
+ numInvocations++;
+ mCurrentUserId = newUserId;
+ }
+ }
+
+ private static class TestStopUserClient extends StopUserClient<Object> {
+ public TestStopUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ protected void startHalOperation() {
+
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ onUserStopped();
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+
+ private static class TestStartUserClient extends StartUserClient<Object, Object> {
+ public TestStartUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStartedCallback<Object> callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ protected void startHalOperation() {
+
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), new Object());
+ callback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 04a7122..0cd6d86 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -24,10 +24,12 @@
import android.content.Context;
import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -56,7 +58,7 @@
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
- private FaceProvider mFaceProvider;
+ private TestableFaceProvider mFaceProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -80,7 +82,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFaceProvider = new FaceProvider(mContext, mSensorProps, TAG,
+ mFaceProvider = new TestableFaceProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher);
}
@@ -123,4 +125,19 @@
assertEquals(0, scheduler.getCurrentPendingCount());
}
}
+
+ private static class TestableFaceProvider extends FaceProvider {
+ public TestableFaceProvider(@NonNull Context context,
+ @NonNull SensorProps[] props,
+ @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ super(context, props, halInstanceName, lockoutResetDispatcher);
+ }
+
+ @Override
+ synchronized IFace getHalInstance() {
+ return mock(IFace.class);
+ }
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index d149880..94cc666 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -24,10 +24,12 @@
import android.content.Context;
import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -59,7 +61,7 @@
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
- private FingerprintProvider mFingerprintProvider;
+ private TestableFingerprintProvider mFingerprintProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -83,7 +85,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new FingerprintProvider(mContext, mSensorProps, TAG,
+ mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@@ -126,4 +128,20 @@
assertEquals(0, scheduler.getCurrentPendingCount());
}
}
+
+ private static class TestableFingerprintProvider extends FingerprintProvider {
+ public TestableFingerprintProvider(@NonNull Context context,
+ @NonNull SensorProps[] props,
+ @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ super(context, props, halInstanceName, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher);
+ }
+
+ @Override
+ synchronized IFingerprint getHalInstance() {
+ return mock(IFingerprint.class);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/content/OWNERS b/services/tests/servicestests/src/com/android/server/content/OWNERS
new file mode 100644
index 0000000..6264a142
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/content/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/content/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index d093e79..c2a81d9 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -16,6 +16,7 @@
package com.android.server.content;
+import android.content.ContentResolver;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
@@ -57,6 +58,23 @@
SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
}
+ public void testSyncExtrasEqualsFails_WithNull() throws Exception {
+ Bundle b1 = new Bundle();
+ b1.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ b1.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+
+ Bundle b2 = new Bundle();
+ b2.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ b2.putString(null, "Hello NPE!");
+ b2.putString("a", "b");
+ b2.putString("c", "d");
+ b2.putString("e", "f");
+
+ assertFalse("Extras not properly compared between bundles.",
+ SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */));
+ }
+
public void testSyncExtrasEqualsFails_differentValues() throws Exception {
Bundle b1 = new Bundle();
Bundle b2 = new Bundle();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 576f9c2..cc206a1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -92,6 +92,7 @@
import android.content.pm.UserInfo;
import android.graphics.Color;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -124,9 +125,7 @@
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
@@ -221,16 +220,6 @@
private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text";
private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text";
- @BeforeClass
- public static void setUpClass() {
- Notification.DevFlags.sForceDefaults = true;
- }
-
- @AfterClass
- public static void tearDownClass() {
- Notification.DevFlags.sForceDefaults = false;
- }
-
@Before
public void setUp() throws Exception {
@@ -4017,53 +4006,59 @@
@Test
public void testUpdateNetworkPreferenceOnStartOnStopUser() throws Exception {
- dpms.handleStartUser(CALLER_USER_HANDLE);
- // TODO(b/178655595)
- // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
- // any(UserHandle.class),
- // anyInt(),
- // any(Executor.class),
- // any(Runnable.class)
- //);
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+ mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL);
- dpms.handleStopUser(CALLER_USER_HANDLE);
- // TODO(b/178655595)
- // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
- // any(UserHandle.class),
- // eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT),
- // any(Executor.class),
- // any(Runnable.class)
- //);
+ dpms.handleStartUser(managedProfileUserId);
+ verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+ eq(UserHandle.of(managedProfileUserId)),
+ anyInt(),
+ any(),
+ any()
+ );
+
+ dpms.handleStopUser(managedProfileUserId);
+ verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+ eq(UserHandle.of(managedProfileUserId)),
+ eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
+ any(),
+ any()
+ );
}
@Test
- public void testGetSetNetworkSlicing() throws Exception {
+ public void testGetSetEnterpriseNetworkPreference() throws Exception {
assertExpectException(SecurityException.class, null,
- () -> dpm.setNetworkSlicingEnabled(false));
+ () -> dpm.setEnterpriseNetworkPreferenceEnabled(false));
assertExpectException(SecurityException.class, null,
- () -> dpm.isNetworkSlicingEnabled());
+ () -> dpm.isEnterpriseNetworkPreferenceEnabled());
- setupProfileOwner();
- dpm.setNetworkSlicingEnabled(false);
- assertThat(dpm.isNetworkSlicingEnabled()).isFalse();
- // TODO(b/178655595)
- // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
- // any(UserHandle.class),
- // eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT),
- // any(Executor.class),
- // any(Runnable.class)
- //);
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
- dpm.setNetworkSlicingEnabled(true);
- assertThat(dpm.isNetworkSlicingEnabled()).isTrue();
- // TODO(b/178655595)
- // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
- // any(UserHandle.class),
- // eq(ConnectivityManager.USER_PREFERENCE_ENTERPRISE),
- // any(Executor.class),
- // any(Runnable.class)
- //);
+ dpm.setEnterpriseNetworkPreferenceEnabled(false);
+ assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isFalse();
+ verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+ eq(UserHandle.of(managedProfileUserId)),
+ eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
+ any(),
+ any()
+ );
+
+ dpm.setEnterpriseNetworkPreferenceEnabled(true);
+ assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isTrue();
+ verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
+ eq(UserHandle.of(managedProfileUserId)),
+ eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE),
+ any(),
+ any()
+ );
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 50ba761..ee9de07 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -107,7 +107,7 @@
}
@Override
- Looper getServiceLooper() {
+ protected Looper getServiceLooper() {
return mTestLooper.getLooper();
}
};
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index aa5bc93..d5df071 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -108,7 +108,7 @@
}
@Override
- Looper getServiceLooper() {
+ protected Looper getServiceLooper() {
return mTestLooper.getLooper();
}
};
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 41f4a1e..8b23be5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -91,7 +91,7 @@
}
@Override
- Looper getServiceLooper() {
+ protected Looper getServiceLooper() {
return mTestLooper.getLooper();
}
};
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index b3ee18d..ee1a857 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -19,21 +19,35 @@
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK;
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
+import static com.android.server.hdmi.Constants.ABORT_REFUSED;
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BACKUP_1;
import static com.android.server.hdmi.Constants.ADDR_BACKUP_2;
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
+import static com.android.server.hdmi.Constants.HANDLED;
+import static com.android.server.hdmi.Constants.MESSAGE_STANDBY;
+import static com.android.server.hdmi.Constants.NOT_HANDLED;
+
+import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import android.content.Context;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Binder;
@@ -44,6 +58,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.SystemService;
import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
import junit.framework.TestCase;
@@ -53,6 +68,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.Optional;
/** Tests for {@link com.android.server.hdmi.HdmiCecController} class. */
@@ -63,27 +79,7 @@
private FakeNativeWrapper mNativeWrapper;
- private class MyHdmiControlService extends HdmiControlService {
-
- MyHdmiControlService(Context context) {
- super(context);
- }
-
- @Override
- Looper getIoLooper() {
- return mMyLooper;
- }
-
- @Override
- Looper getServiceLooper() {
- return mMyLooper;
- }
-
- @Override
- int getCecVersion() {
- return mCecVersion;
- }
- }
+ private HdmiControlService mHdmiControlServiceSpy;
private HdmiCecController mHdmiCecController;
private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
@@ -101,12 +97,39 @@
@Before
public void SetUp() {
mMyLooper = mTestLooper.getLooper();
- mMyLooper = mTestLooper.getLooper();
- HdmiControlService hdmiControlService = new MyHdmiControlService(
- InstrumentationRegistry.getTargetContext());
+
+ mHdmiControlServiceSpy = spy(new HdmiControlService(
+ InstrumentationRegistry.getTargetContext()));
+ doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
+ doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
+ doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
+ doNothing().when(mHdmiControlServiceSpy)
+ .writeStringSystemProperty(anyString(), anyString());
+
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
- hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
+ mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
+ }
+
+ /** Additional setup for tests for onMessage
+ * Adds a local playback device and allocates addresses
+ */
+ public void setUpForOnMessageTest() {
+ mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+
+ HdmiCecLocalDevicePlayback playbackDevice =
+ new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy);
+ playbackDevice.init();
+
+ ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
+ localDevices.add(playbackDevice);
+
+ mHdmiControlServiceSpy.initService();
+ mHdmiControlServiceSpy.allocateLogicalAddress(localDevices,
+ HdmiControlService.INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+ mTestLooper.dispatchAll();
}
/** Tests for {@link HdmiCecController#allocateLogicalAddress} */
@@ -119,7 +142,6 @@
@Test
public void testAllocateLogicalAddress_TvDeviceNonPreferredNotOccupied() {
-
mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback);
mTestLooper.dispatchAll();
assertEquals(ADDR_TV, mLogicalAddress);
@@ -308,4 +330,90 @@
TestCase.assertEquals(Optional.of(callerUid), uidReadingRunnable.getWorkSourceUid());
TestCase.assertEquals(runnerUid, Binder.getCallingWorkSourceUid());
}
+
+ @Test
+ public void onMessage_broadcastMessage_doesNotSendFeatureAbort() {
+ setUpForOnMessageTest();
+
+ doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+ HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+ ADDR_TV, ADDR_BROADCAST);
+
+ mNativeWrapper.onCecMessage(receivedMessage);
+
+ mTestLooper.dispatchAll();
+
+ assertFalse("No <Feature Abort> messages should be sent",
+ mNativeWrapper.getResultMessages().stream().anyMatch(
+ message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT));
+ }
+
+ @Test
+ public void onMessage_notTheDestination_doesNotSendFeatureAbort() {
+ setUpForOnMessageTest();
+
+ doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+ HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+ ADDR_TV, ADDR_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(receivedMessage);
+
+ mTestLooper.dispatchAll();
+
+ assertFalse("No <Feature Abort> messages should be sent",
+ mNativeWrapper.getResultMessages().stream().anyMatch(
+ message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT));
+ }
+
+ @Test
+ public void onMessage_handledMessage_doesNotSendFeatureAbort() {
+ setUpForOnMessageTest();
+
+ doReturn(HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+ HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+ ADDR_TV, ADDR_PLAYBACK_1);
+ mNativeWrapper.onCecMessage(receivedMessage);
+
+ mTestLooper.dispatchAll();
+
+ assertFalse("No <Feature Abort> messages should be sent",
+ mNativeWrapper.getResultMessages().stream().anyMatch(
+ message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT));
+ }
+
+ @Test
+ public void onMessage_unhandledMessage_sendsFeatureAbortUnrecognizedOpcode() {
+ setUpForOnMessageTest();
+
+ doReturn(NOT_HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+ HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+ ADDR_TV, ADDR_PLAYBACK_1);
+ mNativeWrapper.onCecMessage(receivedMessage);
+
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_UNRECOGNIZED_OPCODE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+ }
+
+ @Test
+ public void onMessage_sendsFeatureAbortWithRequestedOperand() {
+ setUpForOnMessageTest();
+
+ doReturn(ABORT_REFUSED).when(mHdmiControlServiceSpy).handleCecCommand(any());
+
+ HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
+ ADDR_TV, ADDR_PLAYBACK_1);
+ mNativeWrapper.onCecMessage(receivedMessage);
+
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_REFUSED);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 6bb148d..38a44c6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -20,7 +20,6 @@
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
-import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
@@ -248,7 +247,8 @@
ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true);
HdmiCecMessage messageGive =
HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -262,45 +262,25 @@
HdmiCecMessage messageGive =
HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@Test
public void handleRequestShortAudioDescriptor_featureDisabled() throws Exception {
- HdmiCecMessage expectedMessage =
- HdmiCecMessageBuilder.buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM,
- ADDR_TV,
- Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
- Constants.ABORT_REFUSED);
-
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false);
- assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
MESSAGE_REQUEST_SAD_LCPM))
- .isTrue();
- mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ .isEqualTo(Constants.ABORT_REFUSED);
}
@Test
public void handleRequestShortAudioDescriptor_samOff() throws Exception {
- HdmiCecMessage expectedMessage =
- HdmiCecMessageBuilder.buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM,
- ADDR_TV,
- Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
- Constants.ABORT_NOT_IN_CORRECT_MODE);
-
mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
- assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
MESSAGE_REQUEST_SAD_LCPM))
- .isEqualTo(true);
- mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE);
}
// Testing device has sadConfig.xml
@@ -315,10 +295,9 @@
Constants.ABORT_UNABLE_TO_DETERMINE);
mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
- assertThat(
- mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
MESSAGE_REQUEST_SAD_LCPM))
- .isEqualTo(true);
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -335,17 +314,18 @@
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
// Check if correctly turned on
mNativeWrapper.clearResultMessages();
expectedMessage =
HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
assertThat(mMusicMute).isFalse();
@@ -365,7 +345,7 @@
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff))
- .isTrue();
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
@@ -373,7 +353,7 @@
expectedMessage =
HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
- .isTrue();
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
assertThat(mMusicMute).isTrue();
@@ -441,7 +421,8 @@
public void handleActiveSource_updateActiveSource() throws Exception {
HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource))
.isTrue();
@@ -513,17 +494,10 @@
public void handleRequestArcInitiate_isNotDirectConnectedToTv() throws Exception {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
- HdmiCecMessage expectedMessage =
- HdmiCecMessageBuilder.buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM,
- ADDR_TV,
- Constants.MESSAGE_REQUEST_ARC_INITIATION,
- Constants.ABORT_NOT_IN_CORRECT_MODE);
mNativeWrapper.setPhysicalAddress(0x1100);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
- mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
+ .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE);
}
@Test
@@ -533,7 +507,8 @@
mNativeWrapper.setPhysicalAddress(0x1000);
mHdmiCecLocalDeviceAudioSystem.removeAction(ArcInitiationActionFromAvr.class);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
.isNotEmpty();
@@ -548,7 +523,8 @@
HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
mHdmiCecLocalDeviceAudioSystem.removeAction(ArcTerminationActionFromAvr.class);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
.isNotEmpty();
@@ -567,7 +543,8 @@
Constants.MESSAGE_REQUEST_ARC_TERMINATION,
Constants.ABORT_NOT_IN_CORRECT_MODE);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
}
@@ -576,17 +553,10 @@
public void handleRequestArcInit_arcIsNotSupported() throws Exception {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
- HdmiCecMessage expectedMessage =
- HdmiCecMessageBuilder.buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM,
- ADDR_TV,
- Constants.MESSAGE_REQUEST_ARC_INITIATION,
- Constants.ABORT_UNRECOGNIZED_OPCODE);
mArcSupport = false;
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
- mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
+ .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE);
}
@Test
@@ -612,7 +582,8 @@
Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
Constants.ABORT_REFUSED);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message))
+ .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -629,7 +600,8 @@
mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -642,7 +614,8 @@
ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
.isEqualTo(expectedActiveSource);
@@ -659,7 +632,8 @@
ActiveSource expectedActiveSource = ActiveSource.of(ADDR_PLAYBACK_1, SELF_PHYSICAL_ADDRESS);
int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
.isEqualTo(expectedActiveSource);
@@ -677,7 +651,8 @@
HdmiCecMessageBuilder.buildRoutingInformation(
ADDR_AUDIO_SYSTEM, HDMI_1_PHYSICAL_ADDRESS);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
@@ -691,7 +666,8 @@
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
}
@@ -727,18 +703,15 @@
int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
ADDR_TV, scaledVolume, mute);
- HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED);
HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
ADDR_AUDIO_SYSTEM);
mNativeWrapper.clearResultMessages();
- boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(expected);
- assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
- assertThat(handled).isTrue();
}
@Test
@@ -758,18 +731,15 @@
int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
ADDR_TV, scaledVolume, mute);
- HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
- ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED);
HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
ADDR_AUDIO_SYSTEM);
mNativeWrapper.clearResultMessages();
- boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus))
+ .isEqualTo(Constants.ABORT_REFUSED);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected);
- assertThat(handled).isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 1a6bad8..80da696 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -113,7 +113,7 @@
}
@Override
- boolean isStandbyMessageReceived() {
+ protected boolean isStandbyMessageReceived() {
return mStandby;
}
@@ -184,7 +184,8 @@
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -205,7 +206,8 @@
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -226,7 +228,8 @@
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -247,7 +250,8 @@
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse();
@@ -270,7 +274,8 @@
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isTrue();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
@@ -293,7 +298,8 @@
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isTrue();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
@@ -309,7 +315,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
0x5000);
@@ -329,7 +336,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
mPlaybackPhysicalAddress);
@@ -349,7 +357,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
mPlaybackPhysicalAddress);
@@ -368,7 +377,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isTrue();
}
@@ -383,7 +393,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
@@ -399,7 +410,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
assertThat(mStandby).isFalse();
}
@@ -461,7 +473,8 @@
mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
0x5000);
@@ -481,7 +494,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
mPlaybackPhysicalAddress);
@@ -501,7 +515,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
mPlaybackPhysicalAddress);
@@ -520,7 +535,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isTrue();
}
@@ -535,7 +551,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
@@ -551,7 +568,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
assertThat(mStandby).isFalse();
}
@@ -606,7 +624,8 @@
public void handleSetStreamPath() {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
- assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+ .isEqualTo(Constants.HANDLED);
}
@Test
@@ -616,7 +635,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetSystemAudioMode(
Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true);
- assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue();
}
@@ -629,7 +649,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetSystemAudioMode(
Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, false);
- assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue();
}
@@ -640,7 +661,8 @@
HdmiCecMessage message =
HdmiCecMessageBuilder.buildReportSystemAudioMode(
Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, true);
- assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue();
}
@@ -883,7 +905,8 @@
mStandby = false;
HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mStandby).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
@@ -900,7 +923,8 @@
HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE);
mStandby = false;
HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mStandby).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
@@ -918,7 +942,8 @@
mStandby = false;
HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mStandby).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
@@ -931,7 +956,8 @@
HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
mStandby = false;
HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mStandby).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
@@ -996,12 +1022,14 @@
// 1. DUT is <AS>.
HdmiCecMessage message1 = HdmiCecMessageBuilder.buildActiveSource(
mHdmiCecLocalDevicePlayback.mAddress, mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
assertThat(mStandby).isFalse();
// 2. DUT loses <AS> and goes to sleep.
HdmiCecMessage message2 = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isTrue();
// 3. DUT becomes <AS> again.
@@ -1271,7 +1299,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
0x5000);
@@ -1290,7 +1319,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isTrue();
}
@@ -1305,7 +1335,8 @@
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
- assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
+ .isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
@@ -1492,7 +1523,8 @@
mHdmiControlService.toggleAndFollowTvPower();
HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
- assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
@@ -1510,7 +1542,8 @@
mHdmiControlService.toggleAndFollowTvPower();
HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
- assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby(
@@ -1525,7 +1558,8 @@
mHdmiControlService.toggleAndFollowTvPower();
HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_STANDBY);
- assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
@@ -1543,7 +1577,8 @@
mHdmiControlService.toggleAndFollowTvPower();
HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV,
mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_UNKNOWN);
- assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus))
+ .isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index b3f0085..6880302 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -198,8 +198,8 @@
ADDR_PLAYBACK_1,
Constants.MESSAGE_CEC_VERSION,
HdmiCecMessage.EMPTY_PARAM);
- boolean handleResult = mHdmiLocalDevice.dispatchMessage(msg);
- assertFalse(handleResult);
+ @Constants.HandleMessageResult int handleResult = mHdmiLocalDevice.dispatchMessage(msg);
+ assertEquals(Constants.NOT_HANDLED, handleResult);
}
@Test
@@ -213,7 +213,7 @@
(byte) (DEVICE_TV & 0xFF)
};
callbackResult = -1;
- boolean handleResult =
+ @Constants.HandleMessageResult int handleResult =
mHdmiLocalDevice.handleGivePhysicalAddress(
(int finalResult) -> callbackResult = finalResult);
mTestLooper.dispatchAll();
@@ -221,7 +221,7 @@
* Test if CecMessage is sent successfully SendMessageResult#SUCCESS is defined in HAL as 0
*/
assertEquals(0, callbackResult);
- assertTrue(handleResult);
+ assertEquals(Constants.HANDLED, handleResult);
}
@Test
@@ -251,85 +251,85 @@
public void handleUserControlPressed_volumeUp() {
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
- assertTrue(result);
+ assertEquals(Constants.HANDLED, result);
}
@Test
public void handleUserControlPressed_volumeDown() {
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
- assertTrue(result);
+ assertEquals(Constants.HANDLED, result);
}
@Test
public void handleUserControlPressed_volumeMute() {
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_MUTE));
- assertTrue(result);
+ assertEquals(Constants.HANDLED, result);
}
@Test
public void handleUserControlPressed_volumeUp_disabled() {
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
- assertFalse(result);
+ assertThat(result).isEqualTo(Constants.ABORT_REFUSED);
}
@Test
public void handleUserControlPressed_volumeDown_disabled() {
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
- assertFalse(result);
+ assertThat(result).isEqualTo(Constants.ABORT_REFUSED);
}
@Test
public void handleUserControlPressed_volumeMute_disabled() {
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
HdmiCecKeycode.CEC_KEYCODE_MUTE));
- assertFalse(result);
+ assertThat(result).isEqualTo(Constants.ABORT_REFUSED);
}
@Test
public void handleCecVersion_isHandled() {
- boolean result = mHdmiLocalDevice.onMessage(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.onMessage(
HdmiCecMessageBuilder.buildCecVersion(ADDR_PLAYBACK_1, mHdmiLocalDevice.mAddress,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
}
@Test
public void handleUserControlPressed_power_localDeviceInStandby_shouldTurnOn() {
mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isTrue();
assertThat(mStandbyMessageReceived).isFalse();
}
@@ -337,11 +337,11 @@
@Test
public void handleUserControlPressed_power_localDeviceOn_shouldNotChangePowerStatus() {
mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isFalse();
assertThat(mStandbyMessageReceived).isFalse();
}
@@ -349,11 +349,11 @@
@Test
public void handleUserControlPressed_powerToggleFunction_localDeviceInStandby_shouldTurnOn() {
mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isTrue();
assertThat(mStandbyMessageReceived).isFalse();
}
@@ -361,11 +361,11 @@
@Test
public void handleUserControlPressed_powerToggleFunction_localDeviceOn_shouldTurnOff() {
mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isFalse();
assertThat(mStandbyMessageReceived).isTrue();
}
@@ -373,11 +373,11 @@
@Test
public void handleUserControlPressed_powerOnFunction_localDeviceInStandby_shouldTurnOn() {
mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isTrue();
assertThat(mStandbyMessageReceived).isFalse();
}
@@ -385,11 +385,11 @@
@Test
public void handleUserControlPressed_powerOnFunction_localDeviceOn_noPowerStatusChange() {
mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isFalse();
assertThat(mStandbyMessageReceived).isFalse();
}
@@ -397,11 +397,11 @@
@Test
public void handleUserControlPressed_powerOffFunction_localDeviceStandby_noPowerStatusChange() {
mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isFalse();
assertThat(mStandbyMessageReceived).isFalse();
}
@@ -409,11 +409,11 @@
@Test
public void handleUserControlPressed_powerOffFunction_localDeviceOn_shouldTurnOff() {
mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
- boolean result = mHdmiLocalDevice.handleUserControlPressed(
+ @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1,
HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION));
- assertThat(result).isTrue();
+ assertEquals(Constants.HANDLED, result);
assertThat(mWakeupMessageReceived).isFalse();
assertThat(mStandbyMessageReceived).isTrue();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 4b3ef2f..39e06a3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -233,7 +233,7 @@
mWokenUp = false;
HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1,
mTvLogicalAddress);
- assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue();
+ assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isTrue();
}
@@ -247,7 +247,7 @@
mWokenUp = false;
HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress,
Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM);
- assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue();
+ assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isTrue();
}
@@ -261,7 +261,7 @@
mWokenUp = false;
HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1,
mTvLogicalAddress);
- assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue();
+ assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isFalse();
}
@@ -275,7 +275,7 @@
mWokenUp = false;
HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress,
Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM);
- assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue();
+ assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mWokenUp).isFalse();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index b5336e3..68aa96a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -28,6 +28,9 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -68,7 +71,7 @@
@RunWith(JUnit4.class)
public class HdmiControlServiceTest {
- private class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
+ protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
private boolean mCanGoToStandby;
private boolean mIsStandby;
@@ -118,7 +121,7 @@
mCanGoToStandby = canGoToStandby;
}
}
- private class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem {
+ protected static class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem {
private boolean mCanGoToStandby;
private boolean mIsStandby;
@@ -171,15 +174,14 @@
private static final String TAG = "HdmiControlServiceTest";
private Context mContextSpy;
- private HdmiControlService mHdmiControlService;
+ private HdmiControlService mHdmiControlServiceSpy;
private HdmiCecController mHdmiCecController;
- private MockAudioSystemDevice mAudioSystemDevice;
- private MockPlaybackDevice mPlaybackDevice;
+ private MockAudioSystemDevice mAudioSystemDeviceSpy;
+ private MockPlaybackDevice mPlaybackDeviceSpy;
private FakeNativeWrapper mNativeWrapper;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- private boolean mStandbyMessageReceived;
private HdmiPortInfo[] mHdmiPortInfo;
@Mock private IPowerManager mIPowerManagerMock;
@@ -199,36 +201,32 @@
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
- mHdmiControlService = new HdmiControlService(mContextSpy) {
- @Override
- boolean isStandbyMessageReceived() {
- return mStandbyMessageReceived;
- }
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy));
+ doNothing().when(mHdmiControlServiceSpy)
+ .writeStringSystemProperty(anyString(), anyString());
- @Override
- protected void writeStringSystemProperty(String key, String value) {
- }
- };
mMyLooper = mTestLooper.getLooper();
- mAudioSystemDevice = new MockAudioSystemDevice(mHdmiControlService);
- mPlaybackDevice = new MockPlaybackDevice(mHdmiControlService);
- mAudioSystemDevice.init();
- mPlaybackDevice.init();
+ mAudioSystemDeviceSpy = spy(new MockAudioSystemDevice(mHdmiControlServiceSpy));
+ mPlaybackDeviceSpy = spy(new MockPlaybackDevice(mHdmiControlServiceSpy));
+ mAudioSystemDeviceSpy.init();
+ mPlaybackDeviceSpy.init();
- mHdmiControlService.setIoLooper(mMyLooper);
- mHdmiControlService.setHdmiCecConfig(hdmiCecConfig);
- mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+ mHdmiControlServiceSpy.setIoLooper(mMyLooper);
+ mHdmiControlServiceSpy.setHdmiCecConfig(hdmiCecConfig);
+ mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
- mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
- mHdmiControlService.setCecController(mHdmiCecController);
- mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
+ mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+ mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create(
+ mHdmiControlServiceSpy));
+ mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator(
+ mHdmiControlServiceSpy));
- mLocalDevices.add(mAudioSystemDevice);
- mLocalDevices.add(mPlaybackDevice);
+ mLocalDevices.add(mAudioSystemDeviceSpy);
+ mLocalDevices.add(mPlaybackDeviceSpy);
mHdmiPortInfo = new HdmiPortInfo[4];
mHdmiPortInfo[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
@@ -239,80 +237,81 @@
mHdmiPortInfo[3] =
new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlService.initService();
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.initService();
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
}
@Test
public void onStandby_notByCec_cannotGoToStandby() {
- mStandbyMessageReceived = false;
- mPlaybackDevice.setCanGoToStandby(false);
+ doReturn(false).when(mHdmiControlServiceSpy).isStandbyMessageReceived();
- mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
- assertTrue(mPlaybackDevice.isStandby());
- assertTrue(mAudioSystemDevice.isStandby());
- assertFalse(mPlaybackDevice.isDisabled());
- assertFalse(mAudioSystemDevice.isDisabled());
+ mPlaybackDeviceSpy.setCanGoToStandby(false);
+
+ mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ assertTrue(mPlaybackDeviceSpy.isStandby());
+ assertTrue(mAudioSystemDeviceSpy.isStandby());
+ assertFalse(mPlaybackDeviceSpy.isDisabled());
+ assertFalse(mAudioSystemDeviceSpy.isDisabled());
}
@Test
public void onStandby_byCec() {
- mStandbyMessageReceived = true;
+ doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived();
- mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
- assertTrue(mPlaybackDevice.isStandby());
- assertTrue(mAudioSystemDevice.isStandby());
- assertTrue(mPlaybackDevice.isDisabled());
- assertTrue(mAudioSystemDevice.isDisabled());
+ mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ assertTrue(mPlaybackDeviceSpy.isStandby());
+ assertTrue(mAudioSystemDeviceSpy.isStandby());
+ assertTrue(mPlaybackDeviceSpy.isDisabled());
+ assertTrue(mAudioSystemDeviceSpy.isDisabled());
}
@Test
public void initialPowerStatus_normalBoot_isTransientToStandby() {
- assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+ assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
}
@Test
public void initialPowerStatus_quiescentBoot_isTransientToStandby() throws RemoteException {
when(mIPowerManagerMock.isInteractive()).thenReturn(false);
- assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+ assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
}
@Test
public void powerStatusAfterBootComplete_normalBoot_isOn() {
- mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
- mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED);
- assertThat(mHdmiControlService.getPowerStatus()).isEqualTo(
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED);
+ assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
}
@Test
public void powerStatusAfterBootComplete_quiescentBoot_isStandby() throws RemoteException {
when(mIPowerManagerMock.isInteractive()).thenReturn(false);
- mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED);
- assertThat(mHdmiControlService.getPowerStatus()).isEqualTo(
+ mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED);
+ assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_STANDBY);
}
@Test
public void initialPowerStatus_normalBoot_goToStandby_doesNotBroadcastsPowerStatus_1_4() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mNativeWrapper.clearResultMessages();
- assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+ assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
- mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
@@ -322,21 +321,21 @@
@Test
public void initialPowerStatus_normalBoot_goToStandby_broadcastsPowerStatus_2_0() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
mTestLooper.dispatchAll();
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mNativeWrapper.clearResultMessages();
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+ assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
- mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
@@ -347,53 +346,53 @@
@Test
public void setAndGetCecVolumeControlEnabled_isApi() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void setAndGetCecVolumeControlEnabled_changesSetting() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- assertThat(mHdmiControlService.readIntSetting(
+ assertThat(mHdmiControlServiceSpy.readIntSetting(
Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- assertThat(mHdmiControlService.readIntSetting(
+ assertThat(mHdmiControlServiceSpy.readIntSetting(
Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void setAndGetCecVolumeControlEnabledInternal_doesNotChangeSetting() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@@ -401,60 +400,61 @@
@Test
public void disableAndReenableCec_volumeControlReturnsToOriginalValue_enabled() {
int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED;
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
- assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(volumeControlEnabled);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl())
+ .isEqualTo(volumeControlEnabled);
}
@Test
public void disableAndReenableCec_volumeControlReturnsToOriginalValue_disabled() {
int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_DISABLED;
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, volumeControlEnabled);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
- assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
volumeControlEnabled);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue(
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
volumeControlEnabled);
}
@Test
public void disableAndReenableCec_volumeControlFeatureListenersNotified() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.VOLUME_CONTROL_ENABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
- mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+ mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
assertThat(callback.mCallbackReceived).isTrue();
assertThat(callback.mVolumeControlEnabled).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
assertThat(callback.mVolumeControlEnabled).isEqualTo(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
}
@Test
public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
- mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+ mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
mTestLooper.dispatchAll();
assertThat(callback.mCallbackReceived).isTrue();
@@ -464,11 +464,11 @@
@Test
public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() {
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
- mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+ mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
mTestLooper.dispatchAll();
assertThat(callback.mCallbackReceived).isTrue();
@@ -478,13 +478,13 @@
@Test
public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() {
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
- mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+ mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
mTestLooper.dispatchAll();
@@ -495,15 +495,15 @@
@Test
public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() {
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
- mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback);
+ mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
mTestLooper.dispatchAll();
- mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback);
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.removeHdmiControlVolumeControlStatusChangeListener(callback);
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
mTestLooper.dispatchAll();
@@ -514,16 +514,16 @@
@Test
public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() {
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback();
VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback();
- mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback1);
- mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2);
+ mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback1);
+ mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback2);
- mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
mTestLooper.dispatchAll();
@@ -537,47 +537,48 @@
@Test
public void getCecVersion_1_4() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
}
@Test
public void getCecVersion_2_0() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_2_0);
}
@Test
public void getCecVersion_change() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_2_0);
}
@Test
public void handleGiveFeatures_cec14_featureAbort() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -592,11 +593,11 @@
@Test
public void handleGiveFeatures_cec20_reportsFeatures() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -606,43 +607,43 @@
HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
- mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(),
- mPlaybackDevice.getDeviceFeatures());
+ mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+ mPlaybackDeviceSpy.getDeviceFeatures());
assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
}
@Test
public void initializeCec_14_doesNotBroadcastReportFeatures() {
mNativeWrapper.clearResultMessages();
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
- mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(),
- mPlaybackDevice.getDeviceFeatures());
+ mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+ mPlaybackDeviceSpy.getDeviceFeatures());
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportFeatures);
}
@Test
public void initializeCec_20_reportsFeaturesBroadcast() {
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures(
Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0,
Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
- mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(),
- mPlaybackDevice.getDeviceFeatures());
+ mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+ mPlaybackDeviceSpy.getDeviceFeatures());
assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
}
@@ -653,7 +654,7 @@
Binder.setCallingWorkSourceUid(callerUid);
WorkSourceUidReadingRunnable uidReadingRunnable = new WorkSourceUidReadingRunnable();
- mHdmiControlService.runOnServiceThread(uidReadingRunnable);
+ mHdmiControlServiceSpy.runOnServiceThread(uidReadingRunnable);
Binder.setCallingWorkSourceUid(runnerUid);
@@ -666,36 +667,36 @@
@Test
public void initCecVersion_limitToMinimumSupportedVersion() {
mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+ assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
}
@Test
public void initCecVersion_limitToAtLeast1_4() {
mNativeWrapper.setCecVersion(0x0);
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+ assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
}
@Test
public void initCecVersion_useHighestMatchingVersion() {
mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.getHdmiCecConfig().setIntValue(
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.getCecVersion()).isEqualTo(
+ assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
HdmiControlManager.HDMI_CEC_VERSION_2_0);
}
@@ -710,4 +711,140 @@
this.mVolumeControlEnabled = enabled;
}
}
+
+ @Test
+ public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() {
+ // Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+ HdmiCecMessage message = HdmiUtils.buildMessage("40:8D:03");
+
+ assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+ .isEqualTo(Constants.ABORT_INVALID_OPERAND);
+ }
+
+ @Test
+ public void handleCecCommand_errorSource_returnsHandled() {
+ // Validity ERROR_SOURCE. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+ HdmiCecMessage message = HdmiUtils.buildMessage("F0:8E");
+
+ assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+ .isEqualTo(Constants.HANDLED);
+
+ }
+
+ @Test
+ public void handleCecCommand_errorDestination_returnsHandled() {
+ // Validity ERROR_DESTINATION. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+ HdmiCecMessage message = HdmiUtils.buildMessage("0F:8E:00");
+
+ assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+ .isEqualTo(Constants.HANDLED);
+ }
+
+ @Test
+ public void handleCecCommand_errorParameterShort_returnsHandled() {
+ // Validity ERROR_PARAMETER_SHORT
+ // Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
+ HdmiCecMessage message = HdmiUtils.buildMessage("40:8E");
+
+ assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+ .isEqualTo(Constants.HANDLED);
+ }
+
+ @Test
+ public void handleCecCommand_notHandledByLocalDevice_returnsNotHandled() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ Constants.ADDR_PLAYBACK_1,
+ HdmiControlManager.POWER_STATUS_ON);
+
+ doReturn(Constants.NOT_HANDLED).when(mHdmiControlServiceSpy)
+ .dispatchMessageToLocalDevice(message);
+
+ assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+ .isEqualTo(Constants.NOT_HANDLED);
+ }
+
+ @Test
+ public void handleCecCommand_handledByLocalDevice_returnsHandled() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ Constants.ADDR_PLAYBACK_1,
+ HdmiControlManager.POWER_STATUS_ON);
+
+ doReturn(Constants.HANDLED).when(mHdmiControlServiceSpy)
+ .dispatchMessageToLocalDevice(message);
+
+ assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+ .isEqualTo(Constants.HANDLED);
+ }
+
+ @Test
+ public void handleCecCommand_localDeviceReturnsFeatureAbort_returnsFeatureAbort() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ Constants.ADDR_PLAYBACK_1,
+ HdmiControlManager.POWER_STATUS_ON);
+
+ doReturn(Constants.ABORT_REFUSED).when(mHdmiControlServiceSpy)
+ .dispatchMessageToLocalDevice(message);
+
+ assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
+ .isEqualTo(Constants.ABORT_REFUSED);
+ }
+
+ @Test
+ public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+ Constants.ADDR_TV,
+ Constants.ADDR_BROADCAST);
+
+ doReturn(Constants.ABORT_REFUSED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+ doReturn(Constants.ABORT_NOT_IN_CORRECT_MODE)
+ .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+ assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+ .isEqualTo(Constants.HANDLED);
+ }
+
+ @Test
+ public void dispatchMessageToLocalDevice_localDevicesDoNotHandleMessage_returnsUnhandled() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+ Constants.ADDR_TV,
+ Constants.ADDR_PLAYBACK_1);
+
+ doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+ doReturn(Constants.NOT_HANDLED)
+ .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+ assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+ .isEqualTo(Constants.NOT_HANDLED);
+ }
+
+ @Test
+ public void dispatchMessageToLocalDevice_localDeviceHandlesMessage_returnsHandled() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+ Constants.ADDR_TV,
+ Constants.ADDR_PLAYBACK_1);
+
+ doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+ doReturn(Constants.HANDLED)
+ .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+ assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+ .isEqualTo(Constants.HANDLED);
+ }
+
+ @Test
+ public void dispatchMessageToLocalDevice_localDeviceReturnsFeatureAbort_returnsFeatureAbort() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
+ Constants.ADDR_TV,
+ Constants.ADDR_PLAYBACK_1);
+
+ doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message);
+ doReturn(Constants.ABORT_REFUSED)
+ .when(mAudioSystemDeviceSpy).dispatchMessage(message);
+
+ assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message))
+ .isEqualTo(Constants.ABORT_REFUSED);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index 91342ce..8c08226 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -21,6 +21,7 @@
import static android.content.pm.UserInfo.FLAG_PROFILE;
import static android.os.UserHandle.USER_SYSTEM;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -110,6 +111,10 @@
public interface MockableRebootEscrowInjected {
int getBootCount();
+ long getCurrentTimeMillis();
+
+ boolean forceServerBased();
+
void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete);
}
@@ -174,6 +179,9 @@
@Override
public boolean serverBasedResumeOnReboot() {
+ if (mInjected.forceServerBased()) {
+ return true;
+ }
return mServerBased;
}
@@ -205,9 +213,20 @@
}
@Override
+ public String getVbmetaDigest(boolean other) {
+ return other ? "" : "fake digest";
+ }
+
+ @Override
+ public long getCurrentTimeMillis() {
+ return mInjected.getCurrentTimeMillis();
+ }
+
+ @Override
public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
int escrowDurationInSeconds, int vbmetaDigestStatus,
int durationSinceBootComplete) {
+
mInjected.reportMetric(success, errorCode, serviceType, attemptCount,
escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete);
}
@@ -430,16 +449,21 @@
// pretend reboot happens here
when(mInjected.getBootCount()).thenReturn(1);
+ when(mInjected.getCurrentTimeMillis()).thenReturn(30000L);
+ mStorage.setLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, 10000L,
+ USER_SYSTEM);
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
eq(0) /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */,
- anyInt(), anyInt(), anyInt());
+ eq(20), eq(0) /* vbmeta status */, anyInt());
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+ assertEquals(mStorage.getLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP,
+ -1, USER_SYSTEM), -1);
}
@Test
@@ -468,7 +492,7 @@
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */,
- anyInt(), anyInt(), anyInt());
+ anyInt(), eq(0) /* vbmeta status */, anyInt());
when(mServiceConnection.unwrap(any(), anyLong()))
.thenAnswer(invocation -> invocation.getArgument(0));
@@ -479,6 +503,84 @@
}
@Test
+ public void loadRebootEscrowDataIfAvailable_ServerBasedRemoteException_Failure()
+ throws Exception {
+ setServerBasedRebootEscrowProvider();
+
+ when(mInjected.getBootCount()).thenReturn(0);
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ // Use x -> x for both wrap & unwrap functions.
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertTrue(mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+ doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
+ metricsErrorCodeCaptor.capture(), eq(2) /* Server based */,
+ eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt());
+
+ when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class);
+ mService.loadRebootEscrowDataIfAvailable(null);
+ verify(mServiceConnection).unwrap(any(), anyLong());
+ assertFalse(metricsSuccessCaptor.getValue());
+ assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
+ metricsErrorCodeCaptor.getValue());
+ }
+
+ @Test
+ public void loadRebootEscrowDataIfAvailable_ServerBasedIoError_RetryFailure() throws Exception {
+ setServerBasedRebootEscrowProvider();
+
+ when(mInjected.getBootCount()).thenReturn(0);
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ // Use x -> x for both wrap & unwrap functions.
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertTrue(mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+ doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
+ metricsErrorCodeCaptor.capture(), eq(2) /* Server based */,
+ eq(2) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt());
+ when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(IOException.class);
+
+ HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
+ thread.start();
+ mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper()));
+ // Sleep 5s for the retry to complete
+ Thread.sleep(5 * 1000);
+ assertFalse(metricsSuccessCaptor.getValue());
+ assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_RETRY_COUNT_EXHAUSTED),
+ metricsErrorCodeCaptor.getValue());
+ }
+
+ @Test
public void loadRebootEscrowDataIfAvailable_ServerBased_RetrySuccess() throws Exception {
setServerBasedRebootEscrowProvider();
@@ -607,9 +709,14 @@
when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
+ // Trigger a vbmeta digest mismatch
+ mStorage.setString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
+ "non sense value", USER_SYSTEM);
mService.loadRebootEscrowDataIfAvailable(null);
verify(mInjected).reportMetric(eq(true), eq(0) /* error code */, eq(1) /* HAL based */,
- eq(1) /* attempt count */, anyInt(), anyInt(), anyInt());
+ eq(1) /* attempt count */, anyInt(), eq(2) /* vbmeta status */, anyInt());
+ assertEquals(mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
+ "", USER_SYSTEM), "");
}
@Test
@@ -636,12 +743,17 @@
when(mInjected.getBootCount()).thenReturn(1);
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+ // Return a null escrow key
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
- anyInt() /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */,
- anyInt(), anyInt(), anyInt());
- when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
+ metricsErrorCodeCaptor.capture(), eq(1) /* HAL based */,
+ eq(1) /* attempt count */, anyInt(), anyInt(), anyInt());
+
+ when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> null);
mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
assertFalse(metricsSuccessCaptor.getValue());
+ assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
+ metricsErrorCodeCaptor.getValue());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 029e9a3..1ab70e5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -48,10 +48,12 @@
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchManager;
import android.app.appsearch.IAppSearchResultCallback;
import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.SearchResultPage;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -159,7 +161,7 @@
case Context.DEVICE_POLICY_SERVICE:
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
- return new AppSearchManager(getTestContext(), mMockAppSearchManager);
+ return new AppSearchManager(this, mMockAppSearchManager);
case Context.ROLE_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
@@ -189,6 +191,12 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
+ return this;
+ }
+
+ @Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
// ignore.
@@ -196,12 +204,6 @@
}
@Override
- public Context createContextAsUser(UserHandle user, int flags) {
- when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
- return this;
- }
-
- @Override
public void unregisterReceiver(BroadcastReceiver receiver) {
// ignore.
}
@@ -238,6 +240,15 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ super.createContextAsUser(user, flags);
+ final ServiceContext ctx = spy(new ServiceContext());
+ when(ctx.getUser()).thenReturn(user);
+ when(ctx.getUserId()).thenReturn(user.getIdentifier());
+ return ctx;
+ }
+
+ @Override
public int getUserId() {
return UserHandle.USER_SYSTEM;
}
@@ -620,6 +631,11 @@
protected Map<String, List<PackageIdentifier>> mSchemasPackageAccessible =
new ArrayMap<>(1);
+ private Map<String, Map<String, GenericDocument>> mDocumentMap = new ArrayMap<>(1);
+
+ private String getKey(int userId, String databaseName) {
+ return new StringBuilder().append(userId).append("@").append(databaseName).toString();
+ }
@Override
public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles,
@@ -653,21 +669,77 @@
public void putDocuments(String packageName, String databaseName,
List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final List<GenericDocument> docs = new ArrayList<>(documentBundles.size());
+ for (Bundle bundle : documentBundles) {
+ docs.add(new GenericDocument(bundle));
+ }
+ final AppSearchBatchResult.Builder<String, Void> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ Map<String, GenericDocument> docMap = mDocumentMap.get(key);
+ for (GenericDocument doc : docs) {
+ builder.setSuccess(doc.getUri(), null);
+ if (docMap == null) {
+ docMap = new ArrayMap<>(1);
+ mDocumentMap.put(key, docMap);
+ }
+ docMap.put(doc.getUri(), doc);
+ }
+ callback.onResult(builder.build());
}
@Override
public void getDocuments(String packageName, String databaseName, String namespace,
List<String> uris, Map<String, List<String>> typePropertyPaths, int userId,
IAppSearchBatchResultCallback callback) throws RemoteException {
- ignore(callback);
+ final AppSearchBatchResult.Builder<String, Bundle> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ for (String uri : uris) {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ key + " not found when getting: " + uri);
+ }
+ } else {
+ final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+ for (String uri : uris) {
+ if (docs.containsKey(uri)) {
+ builder.setSuccess(uri, docs.get(uri).getBundle());
+ } else {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "shortcut not found: " + uri);
+ }
+ }
+ }
+ callback.onResult(builder.build());
}
@Override
public void query(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
+ return;
+ }
+ final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values());
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 0);
+ final ArrayList<Bundle> resultBundles = new ArrayList<>();
+ for (GenericDocument document : documents) {
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putBundle("document", document.getBundle());
+ resultBundle.putString("packageName", packageName);
+ resultBundle.putString("databaseName", databaseName);
+ resultBundle.putParcelableArrayList("matches", new ArrayList<>());
+ resultBundles.add(resultBundle);
+ }
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
}
@Override
@@ -679,7 +751,10 @@
@Override
public void getNextPage(long nextPageToken, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
}
@Override
@@ -698,14 +773,40 @@
public void removeByUri(String packageName, String databaseName, String namespace,
List<String> uris, int userId, IAppSearchBatchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final AppSearchBatchResult.Builder<String, Void> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ for (String uri : uris) {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "package " + key + " not found when removing " + uri);
+ }
+ } else {
+ final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+ for (String uri : uris) {
+ if (docs.containsKey(uri)) {
+ docs.remove(uri);
+ builder.setSuccess(uri, null);
+ } else {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "shortcut not found when removing " + uri);
+ }
+ }
+ }
+ callback.onResult(builder.build());
}
@Override
public void removeByQuery(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ return;
+ }
+ mDocumentMap.get(key).clear();
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
}
@Override
@@ -724,12 +825,12 @@
return null;
}
- private void ignore(IAppSearchResultCallback callback) throws RemoteException {
- callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ private void removeShortcuts() {
+ mDocumentMap.clear();
}
- private void ignore(IAppSearchBatchResultCallback callback) throws RemoteException {
- callback.onResult(new AppSearchBatchResult.Builder().build());
+ private void ignore(IAppSearchResultCallback callback) throws RemoteException {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
}
}
@@ -1146,6 +1247,9 @@
shutdownServices();
+ mMockAppSearchManager.removeShortcuts();
+ mMockAppSearchManager = null;
+
super.tearDown();
}
@@ -1891,6 +1995,11 @@
return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
}
+ protected void updatePackageShortcut(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ mService.updatePackageShortcutForTest(packageName, shortcutId, userId, cb);
+ }
+
protected void assertShortcutExists(String packageName, String shortcutId, int userId) {
assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
}
@@ -2086,6 +2195,10 @@
return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
}
+ protected void updateCallerShortcut(String shortcutId, Consumer<ShortcutInfo> cb) {
+ updatePackageShortcut(getCallingPackage(), shortcutId, getCallingUserId(), cb);
+ }
+
protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
final List<ShortcutInfo>[] ret = new List[1];
runWithCaller(launcher, userId, () -> {
@@ -2245,6 +2358,8 @@
deleteAllSavedFiles();
+ mMockAppSearchManager.removeShortcuts();
+
initService();
mService.applyRestore(payload, USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 4d0beef..3f680e6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1385,6 +1385,7 @@
mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
+ Log.d("ShortcutManagerTest1", si.toString());
assertTrue(si.hasIconFile());
});
@@ -1702,8 +1703,8 @@
// Because setDynamicShortcuts will update the timestamps when ranks are changing,
// we explicitly set timestamps here.
- getCallerShortcut("s1").setTimestamp(5000);
- getCallerShortcut("s2").setTimestamp(1000);
+ updateCallerShortcut("s1", si -> si.setTimestamp(5000));
+ updateCallerShortcut("s2", si -> si.setTimestamp(1000));
setCaller(CALLING_PACKAGE_2);
final ShortcutInfo s2_2 = makeShortcut("s2");
@@ -1713,9 +1714,9 @@
makeComponent(ShortcutActivity.class));
assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
- getCallerShortcut("s2").setTimestamp(1500);
- getCallerShortcut("s3").setTimestamp(3000);
- getCallerShortcut("s4").setTimestamp(500);
+ updateCallerShortcut("s2", si -> si.setTimestamp(1500));
+ updateCallerShortcut("s3", si -> si.setTimestamp(3000));
+ updateCallerShortcut("s4", si -> si.setTimestamp(500));
setCaller(CALLING_PACKAGE_3);
final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
@@ -1723,7 +1724,7 @@
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
- getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
+ updateCallerShortcut("s3", si -> si.setTimestamp(START_TIME + 5000));
setCaller(LAUNCHER_1);
@@ -7686,7 +7687,7 @@
assertEquals("http://www/", si.getIntent().getData().toString());
assertEquals("foo/bar", si.getIntent().getType());
assertEquals(
- new ComponentName("abc", ".xyz"), si.getIntent().getComponent());
+ new ComponentName("abc", "abc.xyz"), si.getIntent().getComponent());
assertEquals(set("cat1", "cat2"), si.getIntent().getCategories());
assertEquals("value1", si.getIntent().getStringExtra("key1"));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 395b643..fc26611 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -58,6 +58,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
import javax.annotation.concurrent.GuardedBy;
@@ -158,6 +159,40 @@
fail("Didn't find a guest: " + list);
}
+ @Test
+ public void testCloneUser() throws Exception {
+ // Test that only one clone user can be created
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ UserInfo userInfo = createProfileForUser("Clone user1",
+ UserManager.USER_TYPE_PROFILE_CLONE,
+ primaryUserId);
+ assertThat(userInfo).isNotNull();
+ UserInfo userInfo2 = createProfileForUser("Clone user2",
+ UserManager.USER_TYPE_PROFILE_CLONE,
+ primaryUserId);
+ assertThat(userInfo2).isNull();
+
+ final Context userContext = mContext.createPackageContextAsUser("system", 0,
+ UserHandle.of(userInfo.id));
+ assertThat(userContext.getSystemService(
+ UserManager.class).sharesMediaWithParent()).isTrue();
+
+ List<UserInfo> list = mUserManager.getUsers();
+ List<UserInfo> cloneUsers = list.stream().filter(
+ user -> (user.id == userInfo.id && user.name.equals("Clone user1")
+ && user.isCloneProfile()))
+ .collect(Collectors.toList());
+ assertThat(cloneUsers.size()).isEqualTo(1);
+
+ // Verify clone user parent
+ assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
+ UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
+ assertThat(parentProfileInfo).isNotNull();
+ assertThat(primaryUserId).isEqualTo(parentProfileInfo.id);
+ removeUser(userInfo.id);
+ assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
+ }
+
@MediumTest
@Test
public void testAdd2Users() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
index 324e592..7903a90 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -72,6 +73,7 @@
private LockSettingsInternal mLockSettingsInternal;
private IBootControl mIBootControl;
private RecoverySystemServiceTestable.IMetricsReporter mMetricsReporter;
+ private RecoverySystemService.PreferencesManager mSharedPreferences;
private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package";
private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package";
@@ -97,10 +99,11 @@
when(mIBootControl.getActiveBootSlot()).thenReturn(1);
mMetricsReporter = mock(RecoverySystemServiceTestable.IMetricsReporter.class);
+ mSharedPreferences = mock(RecoverySystemService.PreferencesManager.class);
mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal,
- mIBootControl, mMetricsReporter);
+ mIBootControl, mMetricsReporter, mSharedPreferences);
}
@Test
@@ -237,6 +240,8 @@
is(true));
verify(mMetricsReporter).reportRebootEscrowPreparationMetrics(
eq(1000), eq(0) /* need preparation */, eq(1) /* client count */);
+ verify(mSharedPreferences).putLong(eq(FAKE_OTA_PACKAGE_NAME
+ + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), eq(100_000L));
}
@@ -245,10 +250,19 @@
IntentSender intentSender = mock(IntentSender.class);
assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
is(true));
+
+ when(mSharedPreferences.getLong(eq(FAKE_OTA_PACKAGE_NAME
+ + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), anyLong()))
+ .thenReturn(200_000L).thenReturn(5000L);
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics(
+ eq(1000), eq(1) /* client count */,
+ eq(-1) /* invalid duration */);
+
mRecoverySystemService.onPreparedForReboot(true);
verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics(
- eq(1000), eq(1) /* client count */, anyInt() /* duration */);
+ eq(1000), eq(1) /* client count */, eq(95) /* duration */);
}
@Test
@@ -352,12 +366,19 @@
public void rebootWithLskf_Success() throws Exception {
assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
mRecoverySystemService.onPreparedForReboot(true);
+
+ when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME
+ + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2);
+ when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF),
+ anyInt())).thenReturn(3);
+ when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF),
+ anyLong())).thenReturn(40_000L);
assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true),
is(true));
verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000),
- eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
- anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+ eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */,
+ anyBoolean(), eq(60) /* duration */, eq(3) /* lskf capture count */);
}
@@ -400,13 +421,19 @@
assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
mRecoverySystemService.onPreparedForReboot(true);
- // Client B's clear won't affect client A's preparation.
+ when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME
+ + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2);
+ when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF),
+ anyInt())).thenReturn(1);
+ when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF),
+ anyLong())).thenReturn(60_000L);
+
assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true),
is(true));
verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000),
- eq(2) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
- anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+ eq(2) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */,
+ anyBoolean(), eq(40), eq(1) /* lskf capture count */);
}
@Test
@@ -415,22 +442,30 @@
mRecoverySystemService.onPreparedForReboot(true);
assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
+ when(mSharedPreferences.getInt(eq(FAKE_OTHER_PACKAGE_NAME
+ + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2);
+ when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF),
+ anyInt())).thenReturn(1);
+ when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF),
+ anyLong())).thenReturn(60_000L);
+
assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true));
assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true),
is(false));
verifyNoMoreInteractions(mIPowerManager);
verify(mMetricsReporter).reportRebootEscrowRebootMetrics(not(eq(0)), eq(1000),
- eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
- anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+ eq(1) /* client count */, anyInt() /* request count */, eq(true) /* slot switch */,
+ anyBoolean(), eq(40), eq(1)/* lskf capture count */);
assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
assertThat(
mRecoverySystemService.rebootWithLskf(FAKE_OTHER_PACKAGE_NAME, "ab-update", true),
is(true));
verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
- verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(2000),
- eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */,
- anyBoolean(), anyInt(), eq(1) /* lskf capture count */);
+
+ verify(mMetricsReporter).reportRebootEscrowRebootMetrics((eq(0)), eq(2000),
+ eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */,
+ anyBoolean(), eq(40), eq(1) /* lskf capture count */);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
index a894178..27e953f 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
@@ -33,11 +33,13 @@
private final LockSettingsInternal mLockSettingsInternal;
private final IBootControl mIBootControl;
private final IMetricsReporter mIMetricsReporter;
+ private final RecoverySystemService.PreferencesManager mSharedPreferences;
MockInjector(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
- IBootControl bootControl, IMetricsReporter metricsReporter) {
+ IBootControl bootControl, IMetricsReporter metricsReporter,
+ RecoverySystemService.PreferencesManager preferences) {
super(context);
mSystemProperties = systemProperties;
mPowerManager = powerManager;
@@ -46,6 +48,7 @@
mLockSettingsInternal = lockSettingsInternal;
mIBootControl = bootControl;
mIMetricsReporter = metricsReporter;
+ mSharedPreferences = preferences;
}
@Override
@@ -114,12 +117,14 @@
requestedClientCount);
}
+ @Override
public void reportRebootEscrowLskfCapturedMetrics(int uid, int requestedClientCount,
int requestedToLskfCapturedDurationInSeconds) {
mIMetricsReporter.reportRebootEscrowLskfCapturedMetrics(uid, requestedClientCount,
requestedToLskfCapturedDurationInSeconds);
}
+ @Override
public void reportRebootEscrowRebootMetrics(int errorCode, int uid, int preparedClientCount,
int requestCount, boolean slotSwitch, boolean serverBased,
int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts) {
@@ -127,14 +132,25 @@
requestCount, slotSwitch, serverBased, lskfCapturedToRebootDurationInSeconds,
lskfCapturedCounts);
}
+
+ @Override
+ public long getCurrentTimeMillis() {
+ return 100_000;
+ }
+
+ @Override
+ public RecoverySystemService.PreferencesManager getMetricsPrefs() {
+ return mSharedPreferences;
+ }
}
RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
- IBootControl bootControl, IMetricsReporter metricsReporter) {
+ IBootControl bootControl, IMetricsReporter metricsReporter,
+ RecoverySystemService.PreferencesManager preferences) {
super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter,
- uncryptSocket, lockSettingsInternal, bootControl, metricsReporter));
+ uncryptSocket, lockSettingsInternal, bootControl, metricsReporter, preferences));
}
public static class FakeSystemProperties {
@@ -176,5 +192,4 @@
int requestCount, boolean slotSwitch, boolean serverBased,
int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts);
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 1068270..742f503 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -217,20 +217,20 @@
fail();
} finally {
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.SET_TIME), anyString());
+ eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
}
}
@Test
public void testSuggestExternalTime() throws Exception {
- doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+ doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion();
mTimeDetectorService.suggestExternalTime(externalTimeSuggestion);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.SET_TIME), anyString());
+ eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
mTestHandler.waitForMessagesToBeProcessed();
mStubbedTimeDetectorStrategy.verifySuggestExternalTimeCalled(externalTimeSuggestion);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 86b1620..8991e9f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -940,7 +940,22 @@
.setLong("elapsed_threshold_restricted", -1);
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
- testTimeout();
+
+ reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+ mController.checkIdleStates(USER_ID);
+ assertBucket(STANDBY_BUCKET_ACTIVE);
+
+ mInjector.mElapsedRealtime = HOUR_MS;
+ mController.checkIdleStates(USER_ID);
+ assertBucket(STANDBY_BUCKET_FREQUENT);
+
+ mInjector.mElapsedRealtime = 2 * HOUR_MS;
+ mController.checkIdleStates(USER_ID);
+ assertBucket(STANDBY_BUCKET_RARE);
+
+ mInjector.mElapsedRealtime = 4 * HOUR_MS;
+ mController.checkIdleStates(USER_ID);
+ assertBucket(STANDBY_BUCKET_RESTRICTED);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
new file mode 100644
index 0000000..dcff479
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 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.vibrator;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link DeviceVibrationEffectAdapter}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:DeviceVibrationEffectAdapterTest
+ */
+@Presubmit
+public class DeviceVibrationEffectAdapterTest {
+ private static final float TEST_MIN_FREQUENCY = 50;
+ private static final float TEST_RESONANT_FREQUENCY = 150;
+ private static final float TEST_FREQUENCY_RESOLUTION = 25;
+ private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+
+ private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
+ new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
+ private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
+ new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY,
+ TEST_RESONANT_FREQUENCY, TEST_FREQUENCY_RESOLUTION,
+ /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP);
+
+ private DeviceVibrationEffectAdapter mAdapter;
+
+ @Before
+ public void setUp() throws Exception {
+ mAdapter = new DeviceVibrationEffectAdapter();
+ }
+
+ @Test
+ public void testPrebakedAndPrimitiveSegments_returnsOriginalSegment() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+ new PrebakedSegment(
+ VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG),
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
+ /* repeatIndex= */ -1);
+
+ assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
+ assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
+ }
+
+ @Test
+ public void testStepAndRampSegments_emptyMapping_returnsSameAmplitudesAndFrequencyZero() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
+ /* startFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 50),
+ new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f,
+ /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)),
+ /* repeatIndex= */ 2);
+
+ VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+ new StepSegment(/* amplitude= */ 0, /* frequency= */ Float.NaN, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ Float.NaN,
+ /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
+ /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
+ /* duration= */ 50),
+ new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f,
+ /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
+ /* duration= */ 20)),
+ /* repeatIndex= */ 2);
+
+ assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
+ }
+
+ @Test
+ public void testStepAndRampSegments_nonEmptyMapping_returnsClippedValues() {
+ VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 1, /* frequency= */ -1, /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+ /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
+ new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+ /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)),
+ /* repeatIndex= */ 2);
+
+ VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+ new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 10),
+ new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 125, /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
+ /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50),
+ new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
+ /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)),
+ /* repeatIndex= */ 2);
+
+ assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
+ }
+
+ private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping) {
+ return new VibratorInfo(/* id= */ 0, /* capabilities= */ 0, null, null,
+ /* qFactor= */ Float.NaN, frequencyMapping);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index b54b696..1e3c344 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -20,6 +20,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
@@ -37,9 +41,9 @@
private static final int EFFECT_DURATION = 20;
- private final Map<Long, VibrationEffect.Prebaked> mEnabledAlwaysOnEffects = new HashMap<>();
- private final List<VibrationEffect> mEffects = new ArrayList<>();
- private final List<Integer> mAmplitudes = new ArrayList<>();
+ private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
+ private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
+ private final List<Float> mAmplitudes = new ArrayList<>();
private final Handler mHandler;
private final FakeNativeWrapper mNativeWrapper;
@@ -57,85 +61,96 @@
public OnVibrationCompleteListener listener;
public boolean isInitialized;
+ @Override
public void init(int vibratorId, OnVibrationCompleteListener listener) {
isInitialized = true;
this.vibratorId = vibratorId;
this.listener = listener;
}
+ @Override
public boolean isAvailable() {
return mIsAvailable;
}
+ @Override
public void on(long milliseconds, long vibrationId) {
- VibrationEffect effect = VibrationEffect.createOneShot(
- milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
- mEffects.add(effect);
+ mEffectSegments.add(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
+ /* frequency= */ 0, (int) milliseconds));
applyLatency();
scheduleListener(milliseconds, vibrationId);
}
+ @Override
public void off() {
}
- public void setAmplitude(int amplitude) {
+ @Override
+ public void setAmplitude(float amplitude) {
mAmplitudes.add(amplitude);
applyLatency();
}
+ @Override
public int[] getSupportedEffects() {
return mSupportedEffects;
}
+ @Override
public int[] getSupportedPrimitives() {
return mSupportedPrimitives;
}
+ @Override
public float getResonantFrequency() {
return mResonantFrequency;
}
+ @Override
public float getQFactor() {
return mQFactor;
}
+ @Override
public long perform(long effect, long strength, long vibrationId) {
if (mSupportedEffects == null
|| Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
return 0;
}
- mEffects.add(new VibrationEffect.Prebaked((int) effect, false, (int) strength));
+ mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength));
applyLatency();
scheduleListener(EFFECT_DURATION, vibrationId);
return EFFECT_DURATION;
}
- public long compose(VibrationEffect.Composition.PrimitiveEffect[] effect,
- long vibrationId) {
- VibrationEffect.Composed composed = new VibrationEffect.Composed(Arrays.asList(effect));
- mEffects.add(composed);
- applyLatency();
+ @Override
+ public long compose(PrimitiveSegment[] effects, long vibrationId) {
long duration = 0;
- for (VibrationEffect.Composition.PrimitiveEffect e : effect) {
- duration += EFFECT_DURATION + e.delay;
+ for (PrimitiveSegment primitive : effects) {
+ duration += EFFECT_DURATION + primitive.getDelay();
+ mEffectSegments.add(primitive);
}
+ applyLatency();
scheduleListener(duration, vibrationId);
return duration;
}
+ @Override
public void setExternalControl(boolean enabled) {
}
+ @Override
public long getCapabilities() {
return mCapabilities;
}
+ @Override
public void alwaysOnEnable(long id, long effect, long strength) {
- VibrationEffect.Prebaked prebaked = new VibrationEffect.Prebaked((int) effect, false,
- (int) strength);
+ PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength);
mEnabledAlwaysOnEffects.put(id, prebaked);
}
+ @Override
public void alwaysOnDisable(long id) {
mEnabledAlwaysOnEffects.remove(id);
}
@@ -222,21 +237,21 @@
* Return the amplitudes set by this controller, including zeroes for each time the vibrator was
* turned off.
*/
- public List<Integer> getAmplitudes() {
+ public List<Float> getAmplitudes() {
return new ArrayList<>(mAmplitudes);
}
- /** Return list of {@link VibrationEffect} played by this controller, in order. */
- public List<VibrationEffect> getEffects() {
- return new ArrayList<>(mEffects);
+ /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */
+ public List<VibrationEffectSegment> getEffectSegments() {
+ return new ArrayList<>(mEffectSegments);
}
/**
- * Return the {@link VibrationEffect.Prebaked} effect enabled with given id, or {@code null} if
+ * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if
* missing or disabled.
*/
@Nullable
- public VibrationEffect.Prebaked getAlwaysOnEffect(int id) {
+ public PrebakedSegment getAlwaysOnEffect(int id) {
return mEnabledAlwaysOnEffects.get((long) id);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
index b6c11fe..59c0b0e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -26,7 +26,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
-import android.os.CombinedVibrationEffect;
import android.os.Handler;
import android.os.IExternalVibratorService;
import android.os.PowerManagerInternal;
@@ -35,6 +34,10 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
@@ -130,51 +133,13 @@
}
@Test
- public void scale_withCombined_resolvesAndScalesRecursively() {
+ public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() {
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_HIGH);
- VibrationEffect prebaked = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- VibrationEffect oneShot = VibrationEffect.createOneShot(10, 10);
+ PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK,
+ /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
- CombinedVibrationEffect.Mono monoScaled = mVibrationScaler.scale(
- CombinedVibrationEffect.createSynced(prebaked),
- VibrationAttributes.USAGE_NOTIFICATION);
- VibrationEffect.Prebaked prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect();
- assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-
- CombinedVibrationEffect.Stereo stereoScaled = mVibrationScaler.scale(
- CombinedVibrationEffect.startSynced()
- .addVibrator(1, prebaked)
- .addVibrator(2, oneShot)
- .combine(),
- VibrationAttributes.USAGE_NOTIFICATION);
- prebakedScaled = (VibrationEffect.Prebaked) stereoScaled.getEffects().get(1);
- assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- VibrationEffect.OneShot oneshotScaled =
- (VibrationEffect.OneShot) stereoScaled.getEffects().get(2);
- assertTrue(oneshotScaled.getAmplitude() > 0);
-
- CombinedVibrationEffect.Sequential sequentialScaled = mVibrationScaler.scale(
- CombinedVibrationEffect.startSequential()
- .addNext(CombinedVibrationEffect.createSynced(prebaked))
- .addNext(CombinedVibrationEffect.createSynced(oneShot))
- .combine(),
- VibrationAttributes.USAGE_NOTIFICATION);
- monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(0);
- prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect();
- assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(1);
- oneshotScaled = (VibrationEffect.OneShot) monoScaled.getEffect();
- assertTrue(oneshotScaled.getAmplitude() > 0);
- }
-
- @Test
- public void scale_withPrebaked_setsEffectStrengthBasedOnSettings() {
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
- VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-
- VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
+ PrebakedSegment scaled = mVibrationScaler.scale(
effect, VibrationAttributes.USAGE_NOTIFICATION);
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
@@ -196,25 +161,33 @@
}
@Test
- public void scale_withPrebakedAndFallback_resolvesAndScalesRecursively() {
+ public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() {
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_HIGH);
- VibrationEffect.OneShot fallback2 = (VibrationEffect.OneShot) VibrationEffect.createOneShot(
- 10, VibrationEffect.DEFAULT_AMPLITUDE);
- VibrationEffect.Prebaked fallback1 = new VibrationEffect.Prebaked(
- VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback2);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback1);
+ VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
- effect, VibrationAttributes.USAGE_NOTIFICATION);
- VibrationEffect.Prebaked scaledFallback1 =
- (VibrationEffect.Prebaked) scaled.getFallbackEffect();
- VibrationEffect.OneShot scaledFallback2 =
- (VibrationEffect.OneShot) scaledFallback1.getFallbackEffect();
+ PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertEquals(scaledFallback1.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertTrue(scaledFallback2.getAmplitude() > 0);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_LOW);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_OFF);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
+ // Unexpected intensity setting will be mapped to STRONG.
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
}
@Test
@@ -224,20 +197,20 @@
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_LOW);
- VibrationEffect.OneShot oneShot = mVibrationScaler.scale(
+ StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
- VibrationAttributes.USAGE_RINGTONE);
- assertTrue(oneShot.getAmplitude() > 0);
+ VibrationAttributes.USAGE_RINGTONE));
+ assertTrue(resolved.getAmplitude() > 0);
- VibrationEffect.Waveform waveform = mVibrationScaler.scale(
+ resolved = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createWaveform(new long[]{10},
new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
- VibrationAttributes.USAGE_RINGTONE);
- assertTrue(waveform.getAmplitudes()[0] > 0);
+ VibrationAttributes.USAGE_RINGTONE));
+ assertTrue(resolved.getAmplitude() > 0);
}
@Test
- public void scale_withOneShotWaveform_scalesAmplitude() {
+ public void scale_withOneShotAndWaveform_scalesAmplitude() {
mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_HIGH);
@@ -248,21 +221,21 @@
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
Vibrator.VIBRATION_INTENSITY_MEDIUM);
- VibrationEffect.OneShot oneShot = mVibrationScaler.scale(
- VibrationEffect.createOneShot(100, 100), VibrationAttributes.USAGE_RINGTONE);
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), VibrationAttributes.USAGE_RINGTONE));
// Ringtone scales up.
- assertTrue(oneShot.getAmplitude() > 100);
+ assertTrue(scaled.getAmplitude() > 0.5);
- VibrationEffect.Waveform waveform = mVibrationScaler.scale(
- VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, -1),
- VibrationAttributes.USAGE_NOTIFICATION);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ VibrationAttributes.USAGE_NOTIFICATION));
// Notification scales down.
- assertTrue(waveform.getAmplitudes()[0] < 100);
+ assertTrue(scaled.getAmplitude() < 0.5);
- oneShot = mVibrationScaler.scale(VibrationEffect.createOneShot(100, 100),
- VibrationAttributes.USAGE_TOUCH);
+ scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
+ VibrationAttributes.USAGE_TOUCH));
// Haptic feedback does not scale.
- assertEquals(100, oneShot.getAmplitude());
+ assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
}
@Test
@@ -280,18 +253,23 @@
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose();
- VibrationEffect.Composed scaled = mVibrationScaler.scale(composed,
- VibrationAttributes.USAGE_RINGTONE);
+ PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed,
+ VibrationAttributes.USAGE_RINGTONE));
// Ringtone scales up.
- assertTrue(scaled.getPrimitiveEffects().get(0).scale > 0.5f);
+ assertTrue(scaled.getScale() > 0.5f);
- scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_NOTIFICATION);
+ scaled = getFirstSegment(mVibrationScaler.scale(composed,
+ VibrationAttributes.USAGE_NOTIFICATION));
// Notification scales down.
- assertTrue(scaled.getPrimitiveEffects().get(0).scale < 0.5f);
+ assertTrue(scaled.getScale() < 0.5f);
- scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH);
+ scaled = getFirstSegment(mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH));
// Haptic feedback does not scale.
- assertEquals(0.5, scaled.getPrimitiveEffects().get(0).scale, 1e-5);
+ assertEquals(0.5, scaled.getScale(), 1e-5);
+ }
+
+ private <T extends VibrationEffectSegment> T getFirstSegment(VibrationEffect.Composed effect) {
+ return (T) effect.getSegments().get(0);
}
private void setUserSetting(String settingName, int value) {
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 7d5eec0..37e0ec2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -40,6 +40,10 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
@@ -61,6 +65,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Tests for {@link VibrationThread}.
@@ -142,8 +147,8 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
@@ -161,7 +166,7 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@@ -183,8 +188,9 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(15)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
- assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+ assertEquals(expectedAmplitudes(1, 2, 3),
+ mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
@@ -213,12 +219,12 @@
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
- List<Integer> playedAmplitudes = fakeVibrator.getAmplitudes();
- assertFalse(fakeVibrator.getEffects().isEmpty());
+ List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
+ assertFalse(fakeVibrator.getEffectSegments().isEmpty());
assertFalse(playedAmplitudes.isEmpty());
for (int i = 0; i < playedAmplitudes.size(); i++) {
- assertEquals(amplitudes[i % amplitudes.length], playedAmplitudes.get(i).intValue());
+ assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5);
}
}
@@ -292,7 +298,7 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
}
@Test
@@ -302,9 +308,10 @@
long vibrationId = 1;
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_STRONG, fallback);
- VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+ Vibration vibration = createVibration(vibrationId, CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
+ vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+ VibrationThread thread = startThreadAndDispatcher(vibration);
waitForCompletion(thread);
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
@@ -314,8 +321,8 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
@@ -331,7 +338,7 @@
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
eq(Vibration.Status.IGNORED_UNSUPPORTED));
- assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
}
@Test
@@ -351,7 +358,10 @@
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
- assertEquals(Arrays.asList(effect), mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ assertEquals(Arrays.asList(
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
}
@Test
@@ -368,7 +378,7 @@
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
eq(Vibration.Status.IGNORED_UNSUPPORTED));
- assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
}
@Test
@@ -428,7 +438,7 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
}
@Test
@@ -454,10 +464,10 @@
assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating());
- VibrationEffect expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
- assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffects());
+ VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffectSegments());
}
@Test
@@ -495,12 +505,16 @@
assertFalse(thread.getVibrators().get(4).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(2).getAmplitudes());
- assertEquals(Arrays.asList(expectedOneShot(20)), mVibratorProviders.get(3).getEffects());
- assertEquals(Arrays.asList(1, 2), mVibratorProviders.get(3).getAmplitudes());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(4).getEffects());
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expectedOneShot(10)),
+ mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(20)),
+ mVibratorProviders.get(3).getEffectSegments());
+ assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
+ assertEquals(Arrays.asList(
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ mVibratorProviders.get(4).getEffectSegments());
}
@Test
@@ -540,11 +554,14 @@
assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating());
- assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+ assertEquals(Arrays.asList(expectedOneShot(10)),
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(Arrays.asList(
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ mVibratorProviders.get(2).getEffectSegments());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(3).getEffects());
+ mVibratorProviders.get(3).getEffectSegments());
}
@Test
@@ -563,8 +580,9 @@
CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(composed);
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
- assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffects().isEmpty()
- && !mVibratorProviders.get(2).getEffects().isEmpty(), thread, TEST_TIMEOUT_MILLIS));
+ assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffectSegments().isEmpty()
+ && !mVibratorProviders.get(2).getEffectSegments().isEmpty(), thread,
+ TEST_TIMEOUT_MILLIS));
thread.syncedVibrationComplete();
waitForCompletion(thread);
@@ -574,8 +592,10 @@
verify(mThreadCallbacks, never()).cancelSyncedVibration();
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+ VibrationEffectSegment expected = expectedPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
}
@Test
@@ -634,10 +654,12 @@
verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId));
verify(mThreadCallbacks, never()).cancelSyncedVibration();
- assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(expectedOneShot(5)), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(200), mVibratorProviders.get(2).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(10)),
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(5)),
+ mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
}
@Test
@@ -704,12 +726,15 @@
assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating());
- assertEquals(Arrays.asList(expectedOneShot(25)), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(expectedOneShot(80)), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(expectedOneShot(60)), mVibratorProviders.get(3).getEffects());
- assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(4, 5), mVibratorProviders.get(2).getAmplitudes());
- assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(25)),
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expectedOneShot(80)),
+ mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(Arrays.asList(expectedOneShot(60)),
+ mVibratorProviders.get(3).getEffectSegments());
+ assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
+ assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
}
@LargeTest
@@ -826,7 +851,7 @@
verify(mVibrationToken).linkToDeath(same(thread), eq(0));
verify(mVibrationToken).unlinkToDeath(same(thread), eq(0));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
- assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+ assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
}
@@ -843,12 +868,16 @@
private VibrationThread startThreadAndDispatcher(long vibrationId,
CombinedVibrationEffect effect) {
- VibrationThread thread = new VibrationThread(createVibration(vibrationId, effect),
- createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks);
+ return startThreadAndDispatcher(createVibration(vibrationId, effect));
+ }
+
+ private VibrationThread startThreadAndDispatcher(Vibration vib) {
+ VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock,
+ mIBatteryStatsMock, mThreadCallbacks);
doAnswer(answer -> {
thread.vibratorComplete(answer.getArgument(0));
return null;
- }).when(mControllerCallbacks).onComplete(anyInt(), eq(vibrationId));
+ }).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id));
mTestLooper.startAutoDispatch();
thread.start();
return thread;
@@ -891,12 +920,21 @@
return array;
}
- private VibrationEffect expectedOneShot(long millis) {
- return VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE);
+ private VibrationEffectSegment expectedOneShot(long millis) {
+ return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, /* frequency= */ 0, (int) millis);
}
- private VibrationEffect expectedPrebaked(int effectId) {
- return new VibrationEffect.Prebaked(effectId, false,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ private VibrationEffectSegment expectedPrebaked(int effectId) {
+ return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ }
+
+ private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) {
+ return new PrimitiveSegment(primitiveId, scale, delay);
+ }
+
+ private List<Float> expectedAmplitudes(int... amplitudes) {
+ return Arrays.stream(amplitudes)
+ .mapToObj(amplitude -> amplitude / 255f)
+ .collect(Collectors.toList());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
index bad3e4c..70ea219 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -39,6 +39,9 @@
import android.os.IVibratorStateListener;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -49,7 +52,6 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -161,9 +163,9 @@
@Test
public void updateAlwaysOn_withCapability_enablesAlwaysOnEffect() {
mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- createController().updateAlwaysOn(1, effect);
+ PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ createController().updateAlwaysOn(1, prebaked);
verify(mNativeWrapperMock).alwaysOnEnable(
eq(1L), eq((long) VibrationEffect.EFFECT_CLICK),
@@ -179,9 +181,9 @@
@Test
public void updateAlwaysOn_withoutCapability_ignoresEffect() {
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- createController().updateAlwaysOn(1, effect);
+ PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ createController().updateAlwaysOn(1, prebaked);
verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong());
verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong());
@@ -201,9 +203,9 @@
when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
VibratorController controller = createController();
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- assertEquals(10L, controller.on(effect, 11));
+ PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertEquals(10L, controller.on(prebaked, 11));
assertTrue(controller.isVibrating());
verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
@@ -216,24 +218,25 @@
when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
- VibrationEffect.Composed effect = (VibrationEffect.Composed)
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .compose();
- assertEquals(15L, controller.on(effect, 12));
-
- ArgumentCaptor<VibrationEffect.Composition.PrimitiveEffect[]> primitivesCaptor =
- ArgumentCaptor.forClass(VibrationEffect.Composition.PrimitiveEffect[].class);
+ PrimitiveSegment[] primitives = new PrimitiveSegment[]{
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+ };
+ assertEquals(15L, controller.on(primitives, 12));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).compose(primitivesCaptor.capture(), eq(12L));
+ verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
+ }
- // Check all primitive effect fields are passed down to the HAL.
- assertEquals(1, primitivesCaptor.getValue().length);
- VibrationEffect.Composition.PrimitiveEffect primitive = primitivesCaptor.getValue()[0];
- assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.id);
- assertEquals(0.5f, primitive.scale, /* delta= */ 1e-2);
- assertEquals(10, primitive.delay);
+ @Test
+ public void on_withComposedPwle_ignoresEffect() {
+ VibratorController controller = createController();
+
+ RampSegment[] primitives = new RampSegment[]{
+ new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+ /* startFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 10)
+ };
+ assertEquals(0L, controller.on(primitives, 12));
+ assertFalse(controller.isVibrating());
}
@Test
@@ -286,4 +289,8 @@
private void mockVibratorCapabilities(int capabilities) {
when(mNativeWrapperMock.getCapabilities()).thenReturn((long) capabilities);
}
+
+ private PrebakedSegment createPrebaked(int effectId, int effectStrength) {
+ return new PrebakedSegment(effectId, /* shouldFallback= */ false, effectStrength);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ce6639c..12ced38 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -65,6 +65,8 @@
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.InputDevice;
@@ -364,13 +366,13 @@
assertTrue(createSystemReadyService().setAlwaysOnEffect(
UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
- VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
+ PrebakedSegment expected = new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
// Only vibrators 1 and 3 have always-on capabilities.
- assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedEffect);
+ assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected);
assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1));
- assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expectedEffect);
+ assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expected);
}
@Test
@@ -388,10 +390,10 @@
assertTrue(createSystemReadyService().setAlwaysOnEffect(
UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
- VibrationEffect.Prebaked expectedClick = new VibrationEffect.Prebaked(
+ PrebakedSegment expectedClick = new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
- VibrationEffect.Prebaked expectedTick = new VibrationEffect.Prebaked(
+ PrebakedSegment expectedTick = new PrebakedSegment(
VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
// Enables click on vibrator 1 and tick on vibrator 2 only.
@@ -487,8 +489,9 @@
vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
- assertEquals(2, mVibratorProviders.get(1).getEffects().size());
- assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(2, mVibratorProviders.get(1).getEffectSegments().size());
+ assertEquals(Arrays.asList(10 / 255f, 100 / 255f),
+ mVibratorProviders.get(1).getAmplitudes());
}
@Test
@@ -500,19 +503,19 @@
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3,
service, TEST_TIMEOUT_MILLIS));
- assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
+ assertEquals(Arrays.asList(2 / 255f, 3 / 255f, 4 / 255f), fakeVibrator.getAmplitudes());
}
@Test
@@ -579,7 +582,7 @@
verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
// VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
+ assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(),
service, /* timeout= */ 50));
}
@@ -640,8 +643,11 @@
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
verify(mNativeWrapperMock).triggerSynced(anyLong());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+
+ PrimitiveSegment expected = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
}
@Test
@@ -665,7 +671,7 @@
.compose())
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -689,7 +695,7 @@
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock, never()).prepareSynced(any());
@@ -709,7 +715,7 @@
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -730,7 +736,7 @@
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -758,40 +764,38 @@
vibrate(service, CombinedVibrationEffect.startSynced()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.combine(), ALARM_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, CombinedVibrationEffect.startSequential()
.addNext(1, VibrationEffect.createOneShot(20, 100))
.combine(), NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose(), HAPTIC_FEEDBACK_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 4,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
- assertEquals(3, fakeVibrator.getEffects().size());
+ assertEquals(4, fakeVibrator.getEffectSegments().size());
assertEquals(1, fakeVibrator.getAmplitudes().size());
// Alarm vibration is always VIBRATION_INTENSITY_HIGH.
- VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
- VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertEquals(expected, fakeVibrator.getEffects().get(0));
+ PrebakedSegment expected = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertEquals(expected, fakeVibrator.getEffectSegments().get(0));
// Notification vibrations will be scaled with SCALE_VERY_HIGH.
- assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
+ assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0));
// Haptic feedback vibrations will be scaled with SCALE_LOW.
- VibrationEffect.Composed played =
- (VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
- assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
- assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
+ assertTrue(0.5 < ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale());
+ assertTrue(0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(3)).getScale());
// Ring vibrations have intensity OFF and are not played.
}
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index c6e35cf..e932905 100644
--- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -93,3 +93,24 @@
spyThrowOnUnmocked<T>(null, block)
inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+/**
+ * Wrapper around [Mockito.any] for generic types.
+ */
+inline fun <reified T> any() = any(T::class.java)
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS
new file mode 100644
index 0000000..d825dfd
--- /dev/null
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pm/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 5462f47..ff88174 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -1688,8 +1688,8 @@
@Override
public boolean matches(VibrationEffect actual) {
- if (actual instanceof VibrationEffect.Waveform &&
- ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+ if (actual instanceof VibrationEffect.Composed
+ && ((VibrationEffect.Composed) actual).getRepeatIndex() == mRepeatIndex) {
return true;
}
// All non-waveform effects are essentially one shots.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index aa9feeaa..55ebe11 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -900,10 +900,10 @@
@Test
public void testDefaultAssistant_overrideDefault() {
- final int userId = 0;
+ final int userId = mContext.getUserId();
final String testComponent = "package/class";
final List<UserInfo> userInfos = new ArrayList<>();
- userInfos.add(new UserInfo(0, "", 0));
+ userInfos.add(new UserInfo(userId, "", 0));
final ArraySet<ComponentName> validAssistants = new ArraySet<>();
validAssistants.add(ComponentName.unflattenFromString(testComponent));
when(mActivityManager.isLowRamDevice()).thenReturn(false);
@@ -2346,7 +2346,7 @@
.thenReturn(mTestNotificationChannel);
reset(mListeners);
- mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+ mBinderService.updateNotificationChannelForPackage(PKG, mUid, mTestNotificationChannel);
verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG),
eq(Process.myUserHandle()), eq(mTestNotificationChannel),
eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
@@ -2882,7 +2882,7 @@
@Test
public void testSetListenerAccessForUser() throws Exception {
- UserHandle user = UserHandle.of(10);
+ UserHandle user = UserHandle.of(mContext.getUserId() + 10);
ComponentName c = ComponentName.unflattenFromString("package/Component");
mBinderService.setNotificationListenerAccessGrantedForUser(
c, user.getIdentifier(), true, true);
@@ -2899,20 +2899,20 @@
@Test
public void testSetAssistantAccessForUser() throws Exception {
- UserHandle user = UserHandle.of(10);
- List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 10;
+ ui.id = mContext.getUserId() + 10;
+ UserHandle user = UserHandle.of(ui.id);
+ List<UserInfo> uis = new ArrayList<>();
uis.add(ui);
ComponentName c = ComponentName.unflattenFromString("package/Component");
- when(mUm.getEnabledProfiles(10)).thenReturn(uis);
+ when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
mBinderService.setNotificationAssistantAccessGrantedForUser(c, user.getIdentifier(), true);
verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
c.flattenToString(), user.getIdentifier(), true, true, true);
- verify(mAssistants).setUserSet(10, true);
+ verify(mAssistants).setUserSet(ui.id, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
c.flattenToString(), user.getIdentifier(), false, true);
verify(mListeners, never()).setPackageOrComponentEnabled(
@@ -2921,7 +2921,7 @@
@Test
public void testGetAssistantAllowedForUser() throws Exception {
- UserHandle user = UserHandle.of(10);
+ UserHandle user = UserHandle.of(mContext.getUserId() + 10);
try {
mBinderService.getAllowedNotificationAssistantForUser(user.getIdentifier());
} catch (IllegalStateException e) {
@@ -2941,12 +2941,12 @@
throw e;
}
}
- verify(mAssistants, times(1)).getAllowedComponents(0);
+ verify(mAssistants, times(1)).getAllowedComponents(mContext.getUserId());
}
@Test
public void testSetDndAccessForUser() throws Exception {
- UserHandle user = UserHandle.of(10);
+ UserHandle user = UserHandle.of(mContext.getUserId() + 10);
ComponentName c = ComponentName.unflattenFromString("package/Component");
mBinderService.setNotificationPolicyAccessGrantedForUser(
c.getPackageName(), user.getIdentifier(), true);
@@ -2966,9 +2966,9 @@
mBinderService.setNotificationListenerAccessGranted(c, true, true);
verify(mListeners, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true, true);
+ c.flattenToString(), mContext.getUserId(), true, true, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, true, true);
+ c.flattenToString(), mContext.getUserId(), false, true, true);
verify(mAssistants, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
}
@@ -2977,7 +2977,7 @@
public void testSetAssistantAccess() throws Exception {
List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 0;
+ ui.id = mContext.getUserId();
uis.add(ui);
when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
ComponentName c = ComponentName.unflattenFromString("package/Component");
@@ -2985,9 +2985,9 @@
mBinderService.setNotificationAssistantAccessGranted(c, true);
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true, true);
+ c.flattenToString(), ui.id, true, true, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, true);
+ c.flattenToString(), ui.id, false, true);
verify(mListeners, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean());
}
@@ -2996,10 +2996,10 @@
public void testSetAssistantAccess_multiProfile() throws Exception {
List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 0;
+ ui.id = mContext.getUserId();
uis.add(ui);
UserInfo ui10 = new UserInfo();
- ui10.id = 10;
+ ui10.id = mContext.getUserId() + 10;
uis.add(ui10);
when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
ComponentName c = ComponentName.unflattenFromString("package/Component");
@@ -3007,13 +3007,14 @@
mBinderService.setNotificationAssistantAccessGranted(c, true);
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true, true);
+ c.flattenToString(), ui.id, true, true, true);
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 10, true, true, true);
+ c.flattenToString(), ui10.id, true, true, true);
+
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, true);
+ c.flattenToString(), ui.id, false, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 10, false, true);
+ c.flattenToString(), ui10.id, false, true);
verify(mListeners, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean());
}
@@ -3026,16 +3027,16 @@
when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList);
List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 0;
+ ui.id = mContext.getUserId();
uis.add(ui);
when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
mBinderService.setNotificationAssistantAccessGranted(null, true);
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, false, true);
+ c.flattenToString(), ui.id, true, false, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, false);
+ c.flattenToString(), ui.id, false, false);
verify(mListeners, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean());
}
@@ -3044,21 +3045,21 @@
public void testSetAssistantAccessForUser_nullWithAllowedAssistant() throws Exception {
List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 10;
+ ui.id = mContext.getUserId() + 10;
uis.add(ui);
UserHandle user = ui.getUserHandle();
ArrayList<ComponentName> componentList = new ArrayList<>();
ComponentName c = ComponentName.unflattenFromString("package/Component");
componentList.add(c);
when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList);
- when(mUm.getEnabledProfiles(10)).thenReturn(uis);
+ when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
mBinderService.setNotificationAssistantAccessGrantedForUser(
null, user.getIdentifier(), true);
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
c.flattenToString(), user.getIdentifier(), true, false, true);
- verify(mAssistants).setUserSet(10, true);
+ verify(mAssistants).setUserSet(ui.id, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
c.flattenToString(), user.getIdentifier(), false, false);
verify(mListeners, never()).setPackageOrComponentEnabled(
@@ -3070,10 +3071,10 @@
throws Exception {
List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 0;
+ ui.id = mContext.getUserId();
uis.add(ui);
UserInfo ui10 = new UserInfo();
- ui10.id = 10;
+ ui10.id = mContext.getUserId() + 10;
uis.add(ui10);
UserHandle user = ui.getUserHandle();
ArrayList<ComponentName> componentList = new ArrayList<>();
@@ -3089,8 +3090,8 @@
c.flattenToString(), user.getIdentifier(), true, false, true);
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
c.flattenToString(), ui10.id, true, false, true);
- verify(mAssistants).setUserSet(0, true);
- verify(mAssistants).setUserSet(10, true);
+ verify(mAssistants).setUserSet(ui.id, true);
+ verify(mAssistants).setUserSet(ui10.id, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
c.flattenToString(), user.getIdentifier(), false, false);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
@@ -3106,7 +3107,7 @@
mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.getPackageName(), 0, true, true);
+ c.getPackageName(), mContext.getUserId(), true, true);
verify(mAssistants, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
verify(mListeners, never()).setPackageOrComponentEnabled(
@@ -3133,7 +3134,7 @@
ComponentName c = ComponentName.unflattenFromString("package/Component");
List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 0;
+ ui.id = mContext.getUserId();
uis.add(ui);
when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
@@ -3168,9 +3169,9 @@
mBinderService.setNotificationListenerAccessGranted(c, true, true);
verify(mListeners, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true, true);
+ c.flattenToString(), mContext.getUserId(), true, true, true);
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, true, true);
+ c.flattenToString(), mContext.getUserId(), false, true, true);
verify(mAssistants, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
}
@@ -3182,7 +3183,7 @@
ComponentName c = ComponentName.unflattenFromString("package/Component");
List<UserInfo> uis = new ArrayList<>();
UserInfo ui = new UserInfo();
- ui.id = 0;
+ ui.id = mContext.getUserId();
uis.add(ui);
when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis);
@@ -3191,9 +3192,9 @@
verify(mListeners, never()).setPackageOrComponentEnabled(
anyString(), anyInt(), anyBoolean(), anyBoolean());
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, true);
+ c.flattenToString(), ui.id, false, true);
verify(mAssistants, times(1)).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true, true);
+ c.flattenToString(), ui.id, true, true, true);
}
@Test
@@ -3207,7 +3208,7 @@
verify(mListeners, never()).setPackageOrComponentEnabled(
anyString(), anyInt(), anyBoolean(), anyBoolean());
verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
- c.getPackageName(), 0, true, true);
+ c.getPackageName(), mContext.getUserId(), true, true);
verify(mAssistants, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index adf8fa4..24b4f65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -330,21 +330,6 @@
assertEquals(startingWin, imeTarget);
}
- @UseTestDisplay(addAllCommonWindows = true)
- @Test
- public void testComputeImeTarget_placeImeToTheTargetRoot() {
- ActivityRecord activity = createActivityRecord(mDisplayContent);
-
- final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
- "startingWin");
- startingWin.setHasSurface(true);
- assertTrue(startingWin.canBeImeTarget());
- DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
-
- WindowState imeTarget = mDisplayContent.computeImeTarget(true /* updateImeTarget */);
- verify(imeTarget.getRootDisplayArea()).placeImeContainer(imeContainer);
- }
-
@Test
public void testUpdateImeParent_forceUpdateRelativeLayer() {
final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index e9c356d..e9907c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -311,6 +311,41 @@
}
@Test
+ public void testPlaceImeContainer_hidesImeWhenParentChanges() {
+ setupImeWindow();
+ final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
+ final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
+ final WindowState firstActivityWin =
+ createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
+ "firstActivityWin");
+ spyOn(firstActivityWin);
+ final WindowState secondActivityWin =
+ createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
+ "secondActivityWin");
+ spyOn(secondActivityWin);
+
+ // firstActivityWin should be the target
+ doReturn(true).when(firstActivityWin).canBeImeTarget();
+ doReturn(false).when(secondActivityWin).canBeImeTarget();
+
+ WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
+ assertThat(imeTarget).isEqualTo(firstActivityWin);
+ verify(mFirstRoot).placeImeContainer(imeContainer);
+
+ // secondActivityWin should be the target
+ doReturn(false).when(firstActivityWin).canBeImeTarget();
+ doReturn(true).when(secondActivityWin).canBeImeTarget();
+
+ spyOn(mDisplay.mInputMethodWindow);
+ imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
+
+ assertThat(imeTarget).isEqualTo(secondActivityWin);
+ verify(mSecondRoot).placeImeContainer(imeContainer);
+ // verify hide() was called on InputMethodWindow.
+ verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */);
+ }
+
+ @Test
public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 925b6f9..6ffdb09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -219,7 +219,7 @@
}
@Test
- public void testAddTasksNoMultiple_expectNoTrim() {
+ public void testAddDocumentTasksNoMultiple_expectNoTrim() {
// Add same non-multiple-task document tasks will remove the task (to re-add it) but not
// trim it
Task documentTask1 = createDocumentTask(".DocumentTask1");
@@ -262,7 +262,7 @@
}
@Test
- public void testAddTasksMultipleDocumentTasks_expectNoTrim() {
+ public void testAddMultipleDocumentTasks_expectNoTrim() {
// Add same multiple-task document tasks does not trim the first tasks
Task documentTask1 = createDocumentTask(".DocumentTask1",
FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -278,9 +278,33 @@
}
@Test
- public void testAddTasksMultipleTasks_expectRemovedNoTrim() {
- // Add multiple same-affinity non-document tasks, ensure that it removes the other task,
- // but that it does not trim it
+ public void testAddTasks_expectRemovedNoTrim() {
+ // Add multiple same-affinity non-document tasks, ensure that it removes, but does not trim
+ // the other task
+ Task task1 = createTaskBuilder(".Task1")
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ Task task2 = createTaskBuilder(".Task1")
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ mRecentTasks.add(task1);
+ assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+ assertThat(mCallbacksRecorder.mAdded).contains(task1);
+ assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
+ assertThat(mCallbacksRecorder.mRemoved).isEmpty();
+ mCallbacksRecorder.clear();
+ mRecentTasks.add(task2);
+ assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+ assertThat(mCallbacksRecorder.mAdded).contains(task2);
+ assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
+ assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
+ assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+ }
+
+ @Test
+ public void testAddMultipleTasks_expectNotRemoved() {
+ // Add multiple same-affinity non-document tasks with MULTIPLE_TASK, ensure that it does not
+ // remove the other task
Task task1 = createTaskBuilder(".Task1")
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.build();
@@ -297,8 +321,7 @@
assertThat(mCallbacksRecorder.mAdded).hasSize(1);
assertThat(mCallbacksRecorder.mAdded).contains(task2);
assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
- assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
- assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+ assertThat(mCallbacksRecorder.mRemoved).isEmpty();
}
@Test
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 6203ae9..f132b49 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -17,13 +17,19 @@
package com.android.server.translation;
import static android.Manifest.permission.MANAGE_UI_TRANSLATION;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Context.TRANSLATION_MANAGER_SERVICE;
import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
+
+import static com.android.internal.util.SyncResultReceiver.bundleFor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
@@ -31,6 +37,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.UserHandle;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.translation.ITranslationManager;
@@ -231,6 +238,46 @@
}
}
+ @Override
+ public void getServiceSettingsActivity(IResultReceiver result, int userId) {
+ final TranslationManagerServiceImpl service;
+ synchronized (mLock) {
+ service = getServiceForUserLocked(userId);
+ }
+ if (service != null) {
+ final ComponentName componentName = service.getServiceSettingsActivityLocked();
+ if (componentName == null) {
+ try {
+ result.send(STATUS_SYNC_CALL_SUCCESS, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
+ }
+ }
+ final Intent intent = new Intent();
+ intent.setComponent(componentName);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final PendingIntent pendingIntent =
+ PendingIntent.getActivityAsUser(getContext(), 0, intent, FLAG_IMMUTABLE,
+ null, new UserHandle(userId));
+ try {
+
+ result.send(STATUS_SYNC_CALL_SUCCESS, bundleFor(pendingIntent));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ try {
+ result.send(STATUS_SYNC_CALL_FAIL, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
+ }
+ }
+ }
+
/**
* Dump the service state into the given stream. You run "adb shell dumpsys translation".
*/
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index ee5ec47..2cd41ba 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -62,6 +62,9 @@
@Nullable
private ServiceInfo mRemoteTranslationServiceInfo;
+ @GuardedBy("mLock")
+ private TranslationServiceInfo mTranslationServiceInfo;
+
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
protected TranslationManagerServiceImpl(
@@ -76,10 +79,10 @@
@Override // from PerUserSystemService
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
throws PackageManager.NameNotFoundException {
- final TranslationServiceInfo info = new TranslationServiceInfo(getContext(),
+ mTranslationServiceInfo = new TranslationServiceInfo(getContext(),
serviceComponent, isTemporaryServiceSetLocked(), mUserId);
- mRemoteTranslationServiceInfo = info.getServiceInfo();
- return info.getServiceInfo();
+ mRemoteTranslationServiceInfo = mTranslationServiceInfo.getServiceInfo();
+ return mTranslationServiceInfo.getServiceInfo();
}
@GuardedBy("mLock")
@@ -227,4 +230,16 @@
}
private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
+
+ public ComponentName getServiceSettingsActivityLocked() {
+ if (mTranslationServiceInfo == null) {
+ return null;
+ }
+ final String activityName = mTranslationServiceInfo.getSettingsActivity();
+ if (activityName == null) {
+ return null;
+ }
+ final String packageName = mTranslationServiceInfo.getServiceInfo().packageName;
+ return new ComponentName(packageName, activityName);
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index e089995a..6541774 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -26,12 +26,12 @@
import android.media.AudioAttributes;
import android.media.AudioRecord;
import android.media.MediaRecorder;
-import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
-import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordRejectedResult;
import android.service.voice.IDspHotwordDetectionCallback;
import android.service.voice.IHotwordDetectionService;
import android.util.Pair;
@@ -77,7 +77,7 @@
boolean mBound;
HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName,
- int userId, boolean bindInstantServiceAllowed, @Nullable Bundle options,
+ int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
mLock = lock;
mContext = context;
@@ -129,7 +129,7 @@
}
}
- void setConfigLocked(Bundle options, SharedMemory sharedMemory) {
+ void setConfigLocked(PersistableBundle options, SharedMemory sharedMemory) {
mRemoteHotwordDetectionService.run(
service -> service.setConfig(options, sharedMemory));
}
@@ -206,13 +206,12 @@
}
@Override
- public void onRejected() throws RemoteException {
+ public void onRejected(HotwordRejectedResult result) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "onRejected");
}
cancelingFuture.cancel(true);
- externalCallback.onRejected(
- AlwaysOnHotwordDetector.HOTWORD_DETECTION_FALSE_ALERT);
+ externalCallback.onRejected(result);
}
};
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 80d4f8f..29354eb 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -56,6 +56,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -983,7 +984,7 @@
}
@Override
- public void setHotwordDetectionServiceConfig(@Nullable Bundle options,
+ public void setHotwordDetectionServiceConfig(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION);
synchronized (this) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index df3ca99..c9b0a3e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -43,6 +43,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -401,7 +402,7 @@
return mInfo.getSupportsLocalInteraction();
}
- public void setHotwordDetectionServiceConfigLocked(@Nullable Bundle options,
+ public void setHotwordDetectionServiceConfigLocked(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
if (DEBUG) {
Slog.d(TAG, "setHotwordDetectionServiceConfigLocked");
@@ -415,7 +416,6 @@
throw new IllegalStateException("Hotword detection service not in isolated process");
}
// TODO : Need to check related permissions for hotword detection service
- // TODO : Sanitize for bundle
if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
Slog.w(TAG, "Can't set sharedMemory to be read-only");
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 04a0aba..3db31ec 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4254,6 +4254,14 @@
public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT =
KEY_PREFIX + "non_rcs_capabilities_cache_expiration_sec_int";
+ /**
+ * Specifies the RCS feature tag allowed for the carrier.
+ *
+ * <p>The values refer to RCC.07 2.4.4.
+ */
+ public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY =
+ KEY_PREFIX + "rcs_feature_tag_allowed_string_array";
+
private Ims() {}
private static PersistableBundle getDefaults() {
@@ -4267,6 +4275,27 @@
defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true);
defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
+ defaults.putStringArray(KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY, new String[]{
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.largemsg\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.deferred\"",
+ "+g.gsma.rcs.cpm.pager-large",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callcomposer\"",
+ "+g.gsma.callcomposer",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callunanswered\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedmap\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedsketch\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geopush\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geosms\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot.sa\"",
+ "+g.gsma.rcs.botversion=\"#=1,#=2\"",
+ "+g.gsma.rcs.cpimext"});
+
return defaults;
}
}
@@ -4849,6 +4878,30 @@
public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL =
"carrier_provisions_wifi_merged_networks_bool";
+ /**
+ * Determines whether or not to use (IP) data connectivity as a supplemental condition to
+ * control the visibility of the no-calling indicator for this carrier in the System UI. Setting
+ * the configuration to true may make sense to a carrier which provides OTT calling.
+ *
+ * Config = true: do not show no-calling indication if (IP) data connectivity is available
+ * or telephony has voice registration.
+ * Config = false: do not show no-calling indication if telephony has voice registration.
+ */
+ public static final String KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL =
+ "hide_no_calling_indicator_on_data_network_bool";
+
+ /**
+ * Determine whether or not to display a call strength indicator for this carrier in the System
+ * UI. Disabling the indication may be reasonable if the carrier's calling is not integrated
+ * into the Android telephony stack (e.g. it is OTT).
+ *
+ * true: Use telephony APIs to detect the current networking medium of calling and display a
+ * UI indication based on the current strength (e.g. signal level) of that medium.
+ * false: Do not display the call strength indicator.
+ */
+ public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL =
+ "display_call_strength_indicator_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -5422,6 +5475,8 @@
sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY,
new String[]{"ia", "default", "ims", "mms", "dun", "emergency"});
sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false);
+ sDefaults.putBoolean(KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL, false);
+ sDefaults.putBoolean(KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL, true);
sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, "");
}
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index 30480d1..a3aaf61 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -26,6 +26,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -47,26 +48,18 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "DEVICE_NR_CAPABILITY_" }, value = {
- DEVICE_NR_CAPABILITY_NONE,
DEVICE_NR_CAPABILITY_NSA,
DEVICE_NR_CAPABILITY_SA,
})
public @interface DeviceNrCapability {}
/**
- * Indicates DEVICE_NR_CAPABILITY_NONE determine that the device does not enable 5G NR.
- * @hide
- */
- @SystemApi
- public static final int DEVICE_NR_CAPABILITY_NONE = 0;
-
- /**
* Indicates DEVICE_NR_CAPABILITY_NSA determine that the device enable the non-standalone
* (NSA) mode of 5G NR.
* @hide
*/
@SystemApi
- public static final int DEVICE_NR_CAPABILITY_NSA = 1 << 0;
+ public static final int DEVICE_NR_CAPABILITY_NSA = 1;
/**
* Indicates DEVICE_NR_CAPABILITY_SA determine that the device enable the standalone (SA)
@@ -74,7 +67,7 @@
* @hide
*/
@SystemApi
- public static final int DEVICE_NR_CAPABILITY_SA = 1 << 1;
+ public static final int DEVICE_NR_CAPABILITY_SA = 2;
static {
ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true);
@@ -83,31 +76,34 @@
List<ModemInfo> logicalModemList = new ArrayList<>();
logicalModemList.add(modemInfo1);
logicalModemList.add(modemInfo2);
+ int[] deviceNrCapabilities = new int[0];
+
DEFAULT_DSDS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false,
- DEVICE_NR_CAPABILITY_NONE);
+ deviceNrCapabilities);
logicalModemList = new ArrayList<>();
logicalModemList.add(modemInfo1);
DEFAULT_SSSS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false,
- DEVICE_NR_CAPABILITY_NONE);
+ deviceNrCapabilities);
}
/**
- * MaxActivePsVoice defines the maximum number of active voice calls. For a dual sim dual
- * standby (DSDS) modem it would be one, but for a dual sim dual active modem it would be 2.
+ * mMaxActiveVoiceSubscriptions defines the maximum subscriptions that can support
+ * simultaneous voice calls. For a dual sim dual standby (DSDS) device it would be one, but
+ * for a dual sim dual active device it would be 2.
*
* @hide
*/
- private final int mMaxActivePsVoice;
+ private final int mMaxActiveVoiceSubscriptions;
/**
- * MaxActiveInternetData defines how many logical modems can have
- * PS attached simultaneously. For example, for L+L modem it
- * should be 2.
+ * mMaxActiveDataSubscriptions defines the maximum subscriptions that can support
+ * simultaneous data connections.
+ * For example, for L+L device it should be 2.
*
* @hide
*/
- private final int mMaxActiveInternetData;
+ private final int mMaxActiveDataSubscriptions;
/**
* Whether modem supports both internet PDN up so
@@ -126,42 +122,45 @@
*
* @hide
*/
- private final int mDeviceNrCapability;
+ private final int[] mDeviceNrCapabilities;
/** @hide */
- public PhoneCapability(int maxActivePsVoice, int maxActiveInternetData,
+ public PhoneCapability(int maxActiveVoiceSubscriptions, int maxActiveDataSubscriptions,
List<ModemInfo> logicalModemList, boolean networkValidationBeforeSwitchSupported,
- int deviceNrCapability) {
- this.mMaxActivePsVoice = maxActivePsVoice;
- this.mMaxActiveInternetData = maxActiveInternetData;
+ int[] deviceNrCapabilities) {
+ this.mMaxActiveVoiceSubscriptions = maxActiveVoiceSubscriptions;
+ this.mMaxActiveDataSubscriptions = maxActiveDataSubscriptions;
// Make sure it's not null.
this.mLogicalModemList = logicalModemList == null ? new ArrayList<>() : logicalModemList;
this.mNetworkValidationBeforeSwitchSupported = networkValidationBeforeSwitchSupported;
- this.mDeviceNrCapability = deviceNrCapability;
+ this.mDeviceNrCapabilities = deviceNrCapabilities;
}
@Override
public String toString() {
- return "mMaxActivePsVoice=" + mMaxActivePsVoice
- + " mMaxActiveInternetData=" + mMaxActiveInternetData
+ return "mMaxActiveVoiceSubscriptions=" + mMaxActiveVoiceSubscriptions
+ + " mMaxActiveDataSubscriptions=" + mMaxActiveDataSubscriptions
+ " mNetworkValidationBeforeSwitchSupported="
+ mNetworkValidationBeforeSwitchSupported
- + " mDeviceNrCapability " + mDeviceNrCapability;
+ + " mDeviceNrCapability " + Arrays.toString(mDeviceNrCapabilities);
}
private PhoneCapability(Parcel in) {
- mMaxActivePsVoice = in.readInt();
- mMaxActiveInternetData = in.readInt();
+ mMaxActiveVoiceSubscriptions = in.readInt();
+ mMaxActiveDataSubscriptions = in.readInt();
mNetworkValidationBeforeSwitchSupported = in.readBoolean();
mLogicalModemList = new ArrayList<>();
in.readList(mLogicalModemList, ModemInfo.class.getClassLoader());
- mDeviceNrCapability = in.readInt();
+ mDeviceNrCapabilities = in.createIntArray();
}
@Override
public int hashCode() {
- return Objects.hash(mMaxActivePsVoice, mMaxActiveInternetData, mLogicalModemList,
- mNetworkValidationBeforeSwitchSupported, mDeviceNrCapability);
+ return Objects.hash(mMaxActiveVoiceSubscriptions,
+ mMaxActiveDataSubscriptions,
+ mLogicalModemList,
+ mNetworkValidationBeforeSwitchSupported,
+ Arrays.hashCode(mDeviceNrCapabilities));
}
@Override
@@ -176,12 +175,12 @@
PhoneCapability s = (PhoneCapability) o;
- return (mMaxActivePsVoice == s.mMaxActivePsVoice
- && mMaxActiveInternetData == s.mMaxActiveInternetData
+ return (mMaxActiveVoiceSubscriptions == s.mMaxActiveVoiceSubscriptions
+ && mMaxActiveDataSubscriptions == s.mMaxActiveDataSubscriptions
&& mNetworkValidationBeforeSwitchSupported
== s.mNetworkValidationBeforeSwitchSupported
&& mLogicalModemList.equals(s.mLogicalModemList)
- && mDeviceNrCapability == s.mDeviceNrCapability);
+ && Arrays.equals(mDeviceNrCapabilities, s.mDeviceNrCapabilities));
}
/**
@@ -195,11 +194,11 @@
* {@link Parcelable#writeToParcel}
*/
public void writeToParcel(@NonNull Parcel dest, @Parcelable.WriteFlags int flags) {
- dest.writeInt(mMaxActivePsVoice);
- dest.writeInt(mMaxActiveInternetData);
+ dest.writeInt(mMaxActiveVoiceSubscriptions);
+ dest.writeInt(mMaxActiveDataSubscriptions);
dest.writeBoolean(mNetworkValidationBeforeSwitchSupported);
dest.writeList(mLogicalModemList);
- dest.writeInt(mDeviceNrCapability);
+ dest.writeIntArray(mDeviceNrCapabilities);
}
public static final @android.annotation.NonNull Parcelable.Creator<PhoneCapability> CREATOR =
@@ -214,25 +213,24 @@
};
/**
- * @return the maximum number of active packet-switched calls. For a dual
- * sim dual standby (DSDS) modem it would be one, but for a dual sim dual active modem it
+ * @return the maximum subscriptions that can support simultaneous voice calls. For a dual
+ * sim dual standby (DSDS) device it would be one, but for a dual sim dual active device it
* would be 2.
* @hide
*/
@SystemApi
- public @IntRange(from = 1) int getMaxActivePacketSwitchedVoiceCalls() {
- return mMaxActivePsVoice;
+ public @IntRange(from = 1) int getMaxActiveVoiceSubscriptions() {
+ return mMaxActiveVoiceSubscriptions;
}
/**
- * @return MaxActiveInternetData defines how many logical modems can have PS attached
- * simultaneously.
- * For example, for L+L modem it should be 2.
+ * @return the maximum subscriptions that can support simultaneous data connections.
+ * For example, for L+L device it should be 2.
* @hide
*/
@SystemApi
- public @IntRange(from = 1) int getMaxActiveInternetData() {
- return mMaxActiveInternetData;
+ public @IntRange(from = 1) int getMaxActiveDataSubscriptions() {
+ return mMaxActiveDataSubscriptions;
}
/**
@@ -254,13 +252,16 @@
}
/**
- * Return the device's NR capability.
+ * Return List of the device's NR capability. If the device doesn't support NR capability,
+ * then this api return empty array.
+ * @see DEVICE_NR_CAPABILITY_NSA
+ * @see DEVICE_NR_CAPABILITY_SA
*
- * @return {@link DeviceNrCapability} the device's NR capability.
+ * @return List of the device's NR capability.
* @hide
*/
@SystemApi
- public @DeviceNrCapability int getDeviceNrCapabilityBitmask() {
- return mDeviceNrCapability;
+ public @NonNull @DeviceNrCapability int[] getDeviceNrCapabilities() {
+ return mDeviceNrCapabilities == null ? (new int[0]) : mDeviceNrCapabilities;
}
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index f110dae..2d06062 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -564,6 +564,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public int getDataRegState() {
return mDataRegState;
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index cfb29f1..5a12865 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -300,9 +300,12 @@
* @param data Message data.
* @param isCdma Indicates weather the type of the SMS is CDMA.
* @return An SmsMessage representing the message.
+ *
+ * @hide
*/
+ @SystemApi
@Nullable
- public static SmsMessage createSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
+ public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
SmsMessageBase wrappedMessage;
if (isCdma) {
@@ -318,23 +321,6 @@
}
/**
- * Create an SmsMessage from a native SMS-Submit PDU, specified by Bluetooth Message Access
- * Profile Specification v1.4.2 5.8.
- * This is used by Bluetooth MAP profile to decode message when sending non UTF-8 SMS messages.
- *
- * @param data Message data.
- * @param isCdma Indicates weather the type of the SMS is CDMA.
- * @return An SmsMessage representing the message.
- *
- * @hide
- */
- @SystemApi
- @Nullable
- public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
- return null;
- }
-
- /**
* Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
* length in bytes (not hex chars) less the SMSC header
*
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index a46621a..67fe783 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -56,6 +56,7 @@
import android.provider.Telephony.SimInfo;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsMmTelManager;
+import android.util.Base64;
import android.util.Log;
import android.util.Pair;
@@ -67,6 +68,11 @@
import com.android.internal.util.Preconditions;
import com.android.telephony.Rlog;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -612,9 +618,9 @@
public static final int D2D_SHARING_ALL_CONTACTS = 1;
/**
- * Device status is shared with all starred contacts.
+ * Device status is shared with all selected contacts.
*/
- public static final int D2D_SHARING_STARRED_CONTACTS = 2;
+ public static final int D2D_SHARING_SELECTED_CONTACTS = 2;
/**
* Device status is shared whenever possible.
@@ -627,7 +633,7 @@
value = {
D2D_SHARING_DISABLED,
D2D_SHARING_ALL_CONTACTS,
- D2D_SHARING_STARRED_CONTACTS,
+ D2D_SHARING_SELECTED_CONTACTS,
D2D_SHARING_ALL
})
public @interface DeviceToDeviceStatusSharingPreference {}
@@ -639,6 +645,13 @@
public static final String D2D_STATUS_SHARING = SimInfo.COLUMN_D2D_STATUS_SHARING;
/**
+ * TelephonyProvider column name for contacts information that allow device to device sharing.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String D2D_STATUS_SHARING_SELECTED_CONTACTS =
+ SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS;
+
+ /**
* TelephonyProvider column name for the color of a SIM.
* <P>Type: INTEGER (int)</P>
*/
@@ -2439,6 +2452,57 @@
}
/**
+ * Serialize list of contacts uri to string
+ * @hide
+ */
+ public static String serializeUriLists(List<Uri> uris) {
+ List<String> contacts = new ArrayList<>();
+ for (Uri uri : uris) {
+ contacts.add(uri.toString());
+ }
+ try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos);
+ oos.writeObject(contacts);
+ oos.flush();
+ return Base64.encodeToString(bos.toByteArray(), Base64.DEFAULT);
+ } catch (IOException e) {
+ logd("serializeUriLists IO exception");
+ }
+ return "";
+ }
+
+ /**
+ * Return list of contacts uri corresponding to query result.
+ * @param subId Subscription Id of Subscription
+ * @param propKey Column name in SubscriptionInfo database
+ * @return list of contacts uri to be returned
+ * @hide
+ */
+ private static List<Uri> getContactsFromSubscriptionProperty(int subId, String propKey,
+ Context context) {
+ String result = getSubscriptionProperty(subId, propKey, context);
+ if (result != null) {
+ try {
+ byte[] b = Base64.decode(result, Base64.DEFAULT);
+ ByteArrayInputStream bis = new ByteArrayInputStream(b);
+ ObjectInputStream ois = new ObjectInputStream(bis);
+ List<String> contacts = ArrayList.class.cast(ois.readObject());
+ List<Uri> uris = new ArrayList<>();
+ for (String contact : contacts) {
+ uris.add(Uri.parse(contact));
+ }
+ return uris;
+ } catch (IOException e) {
+ logd("getContactsFromSubscriptionProperty IO exception");
+ } catch (ClassNotFoundException e) {
+ logd("getContactsFromSubscriptionProperty ClassNotFound exception");
+ }
+ }
+ return new ArrayList<>();
+ }
+
+ /**
* Store properties associated with SubscriptionInfo in database
* @param subId Subscription Id of Subscription
* @param propKey Column name in SubscriptionInfo database
@@ -3443,6 +3507,40 @@
}
/**
+ * Set the list of contacts that allow device to device status sharing for a subscription ID.
+ * The setting app uses this method to indicate with whom they wish to share device to device
+ * status information.
+ * @param contacts The list of contacts that allow device to device status sharing
+ * @param subscriptionId The unique Subscription ID in database
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setDeviceToDeviceStatusSharingContacts(@NonNull List<Uri> contacts,
+ int subscriptionId) {
+ String contactString = serializeUriLists(contacts);
+ if (VDBG) {
+ logd("[setDeviceToDeviceStatusSharingContacts] + contacts: " + contactString
+ + " subId: " + subscriptionId);
+ }
+ setSubscriptionPropertyHelper(subscriptionId, "setDeviceToDeviceSharingStatus",
+ (iSub)->iSub.setDeviceToDeviceStatusSharingContacts(serializeUriLists(contacts),
+ subscriptionId));
+ }
+
+ /**
+ * Returns the list of contacts that allow device to device status sharing.
+ * @param subscriptionId Subscription id of subscription
+ * @return The list of contacts that allow device to device status sharing
+ */
+ public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts(
+ int subscriptionId) {
+ if (VDBG) {
+ logd("[getDeviceToDeviceStatusSharingContacts] + subId: " + subscriptionId);
+ }
+ return getContactsFromSubscriptionProperty(subscriptionId,
+ D2D_STATUS_SHARING_SELECTED_CONTACTS, mContext);
+ }
+
+ /**
* DO NOT USE.
* This API is designed for features that are not finished at this point. Do not call this API.
* @hide
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 2f89bfb..88c66ac 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -84,7 +84,7 @@
* </ul>
* One of the use case is that UX can show a different icon, for example, "5G+"
*/
- public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+ public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5;
@NetworkType
private final int mNetworkType;
@@ -186,7 +186,8 @@
case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
- case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
+ case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+ case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_ADVANCED";
default: return "UNKNOWN";
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d3246ca..d2da51a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14951,6 +14951,13 @@
public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING =
"CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING";
+ /**
+ * Indicates whether {@link #getNetworkSlicingConfiguration} is supported. See comments on
+ * respective methods for more information.
+ */
+ public static final String CAPABILITY_SLICING_CONFIG_SUPPORTED =
+ "CAPABILITY_SLICING_CONFIG_SUPPORTED";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "CAPABILITY_", value = {
@@ -14958,6 +14965,7 @@
CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE,
CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING,
+ CAPABILITY_SLICING_CONFIG_SUPPORTED,
})
public @interface RadioInterfaceCapability {}
@@ -15077,7 +15085,12 @@
* DataThrottlingRequest#DATA_THROTTLING_ACTION_NO_DATA_THROTTLING} can still be requested in
* order to undo the mitigations above it (i.e {@link
* ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_VOICE_ONLY} and/or {@link
- * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}).
+ * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}). </p>
+ *
+ * <p> In addition to the {@link Manifest.permission#MODIFY_PHONE_STATE} permission, callers of
+ * this API must also be listed in the device configuration as an authorized app in
+ * {@code packages/services/Telephony/res/values/config.xml} under the
+ * {@code thermal_mitigation_allowlisted_packages} key. </p>
*
* @param thermalMitigationRequest Thermal mitigation request. See {@link
* ThermalMitigationRequest} for details.
@@ -15096,7 +15109,8 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest);
+ return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest,
+ getOpPackageName());
}
throw new IllegalStateException("telephony service is null.");
} catch (RemoteException ex) {
@@ -15603,9 +15617,15 @@
* <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
* </ul>
*
+ * This will be invalid if the device does not support
+ * android.telephony.TelephonyManager#CAPABILITY_SLICING_CONFIG_SUPPORTED.
+ *
* @param executor the executor on which callback will be invoked.
* @param callback a callback to receive the current slicing configuration.
*/
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED)
@SuppressAutoDoc // No support for carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getNetworkSlicingConfiguration(
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 9493c76..6493772 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -302,4 +302,6 @@
int setUiccApplicationsEnabled(boolean enabled, int subscriptionId);
int setDeviceToDeviceStatusSharing(int sharing, int subId);
+
+ int setDeviceToDeviceStatusSharingContacts(String contacts, int subscriptionId);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8ed9cff..46752b7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2250,10 +2250,12 @@
*
* @param subId the id of the subscription
* @param thermalMitigationRequest holds the parameters necessary for the request.
+ * @param callingPackage the package name of the calling package.
* @throws InvalidThermalMitigationRequestException if the parametes are invalid.
*/
int sendThermalMitigationRequest(int subId,
- in ThermalMitigationRequest thermalMitigationRequest);
+ in ThermalMitigationRequest thermalMitigationRequest,
+ String callingPackage);
/**
* get the Generic Bootstrapping Architecture authentication keys
@@ -2349,6 +2351,16 @@
boolean getCarrierSingleRegistrationEnabled(int subId);
/**
+ * Overrides the ims feature validation result
+ */
+ boolean setImsFeatureValidationOverride(int subId, String enabled);
+
+ /**
+ * Gets the ims feature validation override value
+ */
+ boolean getImsFeatureValidationOverride(int subId);
+
+ /**
* Return the mobile provisioning url that is used to launch a browser to allow users to manage
* their mobile plan.
*/
diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
index 4f95ce5..b134fe7 100644
--- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
@@ -17,6 +17,7 @@
package com.android.test.input
import android.os.HandlerThread
+import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.Looper
import android.view.InputChannel
import android.view.InputEvent
@@ -24,7 +25,8 @@
import android.view.InputEventSender
import android.view.KeyEvent
import android.view.MotionEvent
-import java.util.concurrent.CountDownLatch
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
import org.junit.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -44,41 +46,44 @@
assertEquals(expected.displayId, received.displayId)
}
+private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T {
+ try {
+ return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS)
+ } catch (e: InterruptedException) {
+ throw RuntimeException("Unexpectedly interrupted while waiting for event")
+ }
+}
+
class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
InputEventReceiver(channel, looper) {
- companion object {
- const val TAG = "TestInputEventReceiver"
- }
-
- var lastEvent: InputEvent? = null
+ private val mInputEvents = LinkedBlockingQueue<InputEvent>()
override fun onInputEvent(event: InputEvent) {
- lastEvent = when (event) {
- is KeyEvent -> KeyEvent.obtain(event)
- is MotionEvent -> MotionEvent.obtain(event)
+ when (event) {
+ is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event))
+ is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event))
else -> throw Exception("Received $event is neither a key nor a motion")
}
finishInputEvent(event, true /*handled*/)
}
+
+ fun getInputEvent(): InputEvent {
+ return getEvent(mInputEvents)
+ }
}
class TestInputEventSender(channel: InputChannel, looper: Looper) :
InputEventSender(channel, looper) {
- companion object {
- const val TAG = "TestInputEventSender"
- }
- data class FinishedResult(val seq: Int, val handled: Boolean)
+ data class FinishedSignal(val seq: Int, val handled: Boolean)
- private var mFinishedSignal = CountDownLatch(1)
+ private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
+
override fun onInputEventFinished(seq: Int, handled: Boolean) {
- finishedResult = FinishedResult(seq, handled)
- mFinishedSignal.countDown()
+ mFinishedSignals.put(FinishedSignal(seq, handled))
}
- lateinit var finishedResult: FinishedResult
- fun waitForFinish() {
- mFinishedSignal.await()
- mFinishedSignal = CountDownLatch(1) // Ready for next event
+ fun getFinishedSignal(): FinishedSignal {
+ return getEvent(mFinishedSignals)
}
}
@@ -111,13 +116,13 @@
KeyEvent.KEYCODE_A, 0 /*repeat*/)
val seq = 10
mSender.sendInputEvent(seq, key)
- mSender.waitForFinish()
+ val receivedKey = mReceiver.getInputEvent() as KeyEvent
+ val finishedSignal = mSender.getFinishedSignal()
// Check receiver
- assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent)
+ assertKeyEvent(key, receivedKey)
// Check sender
- assertEquals(seq, mSender.finishedResult.seq)
- assertEquals(true, mSender.finishedResult.handled)
+ assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal)
}
}
diff --git a/tests/UsesFeature2Test/AndroidManifest.xml b/tests/UsesFeature2Test/AndroidManifest.xml
index 8caf4a1..1f1a909 100644
--- a/tests/UsesFeature2Test/AndroidManifest.xml
+++ b/tests/UsesFeature2Test/AndroidManifest.xml
@@ -22,6 +22,9 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-feature android:name="android.hardware.sensor.accelerometer" />
<feature-group android:label="@string/minimal">
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index fd126ad..1e54093 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertParcelSane
@@ -44,7 +45,13 @@
setPartialConnectivityAcceptable(false)
setUnvalidatedConnectivityAcceptable(true)
}.build()
- assertParcelSane(config, 12)
+ if (isAtLeastS()) {
+ // From S, the config will have 12 items
+ assertParcelSane(config, 12)
+ } else {
+ // For R or below, the config will have 10 items
+ assertParcelSane(config, 10)
+ }
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index f161e52..e7718b5 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -38,14 +38,12 @@
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
-import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
-import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
import static android.os.Process.INVALID_UID;
import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
@@ -103,20 +101,6 @@
@Test
public void testMaybeMarkCapabilitiesRestricted() {
- // verify EIMS is restricted
- assertEquals((1 << NET_CAPABILITY_EIMS) & RESTRICTED_CAPABILITIES,
- (1 << NET_CAPABILITY_EIMS));
-
- // verify CBS is also restricted
- assertEquals((1 << NET_CAPABILITY_CBS) & RESTRICTED_CAPABILITIES,
- (1 << NET_CAPABILITY_CBS));
-
- // verify default is not restricted
- assertEquals((1 << NET_CAPABILITY_INTERNET) & RESTRICTED_CAPABILITIES, 0);
-
- // just to see
- assertEquals(RESTRICTED_CAPABILITIES & UNRESTRICTED_CAPABILITIES, 0);
-
// check that internet does not get restricted
NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -329,7 +313,8 @@
if (isAtLeastS()) {
netCap.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2));
netCap.setUids(uids);
- } else if (isAtLeastR()) {
+ }
+ if (isAtLeastR()) {
netCap.setOwnerUid(123);
netCap.setAdministratorUids(new int[] {5, 11});
}
@@ -611,17 +596,18 @@
// From S, it is not allowed to have the same capability in both wanted and
// unwanted list.
assertThrows(IllegalArgumentException.class, () -> nc2.combineCapabilities(nc1));
+ // Remove unwanted capability to continue other tests.
+ nc1.removeUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
} else {
nc2.combineCapabilities(nc1);
// We will get this capability in both requested and unwanted lists thus this request
// will never be satisfied.
assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+ // For R or below, remove unwanted capability via removeCapability.
+ nc1.removeCapability(NET_CAPABILITY_NOT_ROAMING);
}
- // Remove unwanted capability to continue other tests.
- nc1.removeUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
-
nc1.setSSID(TEST_SSID);
nc2.combineCapabilities(nc1);
if (isAtLeastR()) {
@@ -983,26 +969,6 @@
assertNotEquals(-50, nc.getSignalStrength());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- public void testDeduceRestrictedCapability() {
- final NetworkCapabilities nc = new NetworkCapabilities();
- // Default capabilities don't have restricted capability.
- assertFalse(nc.deduceRestrictedCapability());
- // If there is a force restricted capability, then the network capabilities is restricted.
- nc.addCapability(NET_CAPABILITY_OEM_PAID);
- nc.addCapability(NET_CAPABILITY_INTERNET);
- assertTrue(nc.deduceRestrictedCapability());
- // Except for the force restricted capability, if there is any unrestricted capability in
- // capabilities, then the network capabilities is not restricted.
- nc.removeCapability(NET_CAPABILITY_OEM_PAID);
- nc.addCapability(NET_CAPABILITY_CBS);
- assertFalse(nc.deduceRestrictedCapability());
- // Except for the force restricted capability, the network capabilities will only be treated
- // as restricted when there is no any unrestricted capability.
- nc.removeCapability(NET_CAPABILITY_INTERNET);
- assertTrue(nc.deduceRestrictedCapability());
- }
-
private void assertNoTransport(NetworkCapabilities nc) {
for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
assertFalse(nc.hasTransport(i));
diff --git a/tests/net/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
index 01eb514..61ef5bd 100644
--- a/tests/net/integration/src/android/net/TestNetworkStackClient.kt
+++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
@@ -19,6 +19,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.net.networkstack.NetworkStackClientBase
import android.os.IBinder
import com.android.server.net.integrationtests.TestNetworkStackService
import org.mockito.Mockito.any
@@ -29,28 +30,22 @@
const val TEST_ACTION_SUFFIX = ".Test"
-class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+class TestNetworkStackClient(private val context: Context) : NetworkStackClientBase() {
// TODO: consider switching to TrackRecord for more expressive checks
private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+ private val moduleConnector = ConnectivityModuleConnector { _, action, _, _ ->
+ val intent = Intent(action)
+ val serviceName = TestNetworkStackService::class.qualifiedName
+ ?: fail("TestNetworkStackService name not found")
+ intent.component = ComponentName(context.packageName, serviceName)
+ return@ConnectivityModuleConnector intent
+ }.also { it.init(context) }
- private class TestDependencies(private val context: Context) : Dependencies {
- override fun addToServiceManager(service: IBinder) = Unit
- override fun checkCallerUid() = Unit
-
- override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
- return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
- getNetworkStackIntent(inSystemProcess)
- }.also { it.init(context) }
- }
-
- private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
- // Simulate out-of-system-process config: in-process service not found (null intent)
- if (inSystemProcess) return null
- val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
- val serviceName = TestNetworkStackService::class.qualifiedName
- ?: fail("TestNetworkStackService name not found")
- intent.component = ComponentName(context.packageName, serviceName)
- return intent
+ fun start() {
+ moduleConnector.startModuleService(
+ INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) { connector ->
+ onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(connector))
}
}
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index db49e0b..14dddcbd 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -157,7 +157,6 @@
doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
networkStackClient = TestNetworkStackClient(realContext)
- networkStackClient.init()
networkStackClient.start()
service = TestConnectivityService(makeDependencies())
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 6fc605e..36f205b 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -64,6 +64,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.os.Process;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -219,8 +220,8 @@
ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
// register callback
- when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(),
- anyInt(), any(), nullable(String.class))).thenReturn(request);
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+ anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request);
manager.requestNetwork(request, callback, handler);
// callback triggers
@@ -247,8 +248,8 @@
ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
// register callback
- when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(),
- anyInt(), any(), nullable(String.class))).thenReturn(req1);
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+ anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1);
manager.requestNetwork(req1, callback, handler);
// callback triggers
@@ -265,8 +266,8 @@
verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
// callback can be registered again
- when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(),
- anyInt(), any(), nullable(String.class))).thenReturn(req2);
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+ anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2);
manager.requestNetwork(req2, callback, handler);
// callback triggers
@@ -289,8 +290,8 @@
info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
when(mCtx.getApplicationInfo()).thenReturn(info);
- when(mService.requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(),
- any(), nullable(String.class))).thenReturn(request);
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(),
+ anyInt(), any(), nullable(String.class))).thenReturn(request);
Handler handler = new Handler(Looper.getMainLooper());
manager.requestNetwork(request, callback, handler);
@@ -357,34 +358,40 @@
final NetworkCallback callback = new ConnectivityManager.NetworkCallback();
manager.requestNetwork(request, callback);
- verify(mService).requestNetwork(eq(request.networkCapabilities),
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
eq(testPkgName), eq(testAttributionTag));
reset(mService);
// Verify that register network callback does not calls requestNetwork at all.
manager.registerNetworkCallback(request, callback);
- verify(mService, never()).requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(),
- anyInt(), any(), any());
+ verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(),
+ anyInt(), anyInt(), any(), any());
verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(),
eq(testPkgName), eq(testAttributionTag));
reset(mService);
+ Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+
manager.registerDefaultNetworkCallback(callback);
- verify(mService).requestNetwork(eq(null),
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
eq(testPkgName), eq(testAttributionTag));
reset(mService);
- Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+ manager.registerDefaultNetworkCallbackAsUid(42, callback, handler);
+ verify(mService).requestNetwork(eq(42), eq(null),
+ eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
+ eq(testPkgName), eq(testAttributionTag));
+
manager.requestBackgroundNetwork(request, handler, callback);
- verify(mService).requestNetwork(eq(request.networkCapabilities),
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
eq(testPkgName), eq(testAttributionTag));
reset(mService);
manager.registerSystemDefaultNetworkCallback(callback, handler);
- verify(mService).requestNetwork(eq(null),
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
eq(testPkgName), eq(testAttributionTag));
reset(mService);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 0c2fb4e..d82565a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -33,7 +33,6 @@
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
-import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
@@ -216,7 +215,6 @@
import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
-import android.net.NetworkStackClient;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTestResultParcelable;
import android.net.OemNetworkPreferences;
@@ -236,6 +234,7 @@
import android.net.VpnManager;
import android.net.VpnTransportInfo;
import android.net.metrics.IpConnectivityLog;
+import android.net.networkstack.NetworkStackClientBase;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.NetworkMonitorUtils;
@@ -446,7 +445,7 @@
@Mock NetworkStatsManager mStatsManager;
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
- @Mock NetworkStackClient mNetworkStack;
+ @Mock NetworkStackClientBase mNetworkStack;
@Mock PackageManager mPackageManager;
@Mock UserManager mUserManager;
@Mock NotificationManager mNotificationManager;
@@ -1201,12 +1200,10 @@
mNetworkCapabilities);
mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
- final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
- : mMockVpn.getNetwork().getNetId();
- verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
+ verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(uids)));
verify(mMockNetd, never())
- .networkRemoveUidRanges(eq(expectedNetId), any());
+ .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), any());
mAgentRegistered = true;
updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
@@ -1448,6 +1445,23 @@
});
}
+ private interface ExceptionalRunnable {
+ void run() throws Exception;
+ }
+
+ private void withPermission(String permission, ExceptionalRunnable r) throws Exception {
+ if (mServiceContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ r.run();
+ return;
+ }
+ try {
+ mServiceContext.setPermission(permission, PERMISSION_GRANTED);
+ r.run();
+ } finally {
+ mServiceContext.setPermission(permission, PERMISSION_DENIED);
+ }
+ }
+
private static final int PRIMARY_USER = 0;
private static final UidRange PRIMARY_UIDRANGE =
UidRange.createForUser(UserHandle.of(PRIMARY_USER));
@@ -1556,7 +1570,7 @@
doReturn(mNetworkStack).when(deps).getNetworkStack();
doReturn(mSystemProperties).when(deps).getSystemProperties();
doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
- doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
+ doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any());
doAnswer(inv -> {
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -3811,8 +3825,9 @@
NetworkCapabilities networkCapabilities = new NetworkCapabilities();
networkCapabilities.addTransportType(TRANSPORT_WIFI)
.setNetworkSpecifier(new MatchAllNetworkSpecifier());
- mService.requestNetwork(networkCapabilities, NetworkRequest.Type.REQUEST.ordinal(),
- null, 0, null, ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
+ mService.requestNetwork(Process.INVALID_UID, networkCapabilities,
+ NetworkRequest.Type.REQUEST.ordinal(), null, 0, null,
+ ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
mContext.getPackageName(), getAttributionTag());
});
@@ -4041,7 +4056,7 @@
}
@Test
- public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
+ public void testRegisterPrivilegedDefaultCallbacksRequireNetworkSettings() throws Exception {
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false /* validated */);
@@ -4050,12 +4065,19 @@
assertThrows(SecurityException.class,
() -> mCm.registerSystemDefaultNetworkCallback(callback, handler));
callback.assertNoCallback();
+ assertThrows(SecurityException.class,
+ () -> mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler));
+ callback.assertNoCallback();
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
PERMISSION_GRANTED);
mCm.registerSystemDefaultNetworkCallback(callback, handler);
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
mCm.unregisterNetworkCallback(callback);
+
+ mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler);
+ callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ mCm.unregisterNetworkCallback(callback);
}
private void setCaptivePortalMode(int mode) {
@@ -7488,6 +7510,10 @@
final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback();
registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID);
+ final TestNetworkCallback vpnDefaultCallbackAsUid = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallbackAsUid(VPN_UID, vpnDefaultCallbackAsUid,
+ new Handler(ConnectivityThread.getInstanceLooper()));
+
final int uid = Process.myUid();
final int userId = UserHandle.getUserId(uid);
final ArrayList<String> allowList = new ArrayList<>();
@@ -7507,6 +7533,7 @@
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7520,6 +7547,7 @@
defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -7535,6 +7563,7 @@
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
// The following requires that the UID of this test package is greater than VPN_UID. This
// is always true in practice because a plain AOSP build with no apps installed has almost
@@ -7556,6 +7585,7 @@
defaultCallback.assertNoCallback();
vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7577,6 +7607,7 @@
assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7589,6 +7620,7 @@
assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7604,6 +7636,7 @@
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7616,6 +7649,7 @@
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7629,6 +7663,7 @@
assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7640,6 +7675,7 @@
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability.
vpnUidDefaultCallback.assertNoCallback(); // VPN does not apply to VPN_UID
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7652,12 +7688,14 @@
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
+ vpnDefaultCallbackAsUid.assertNoCallback();
assertNull(mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultCallback);
mCm.unregisterNetworkCallback(vpnUidCallback);
mCm.unregisterNetworkCallback(vpnUidDefaultCallback);
+ mCm.unregisterNetworkCallback(vpnDefaultCallbackAsUid);
}
private void setupLegacyLockdownVpn() {
@@ -9758,14 +9796,13 @@
exemptUidCaptor.capture());
assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
- final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
- : mMockVpn.getNetwork().getNetId();
-
if (add) {
- inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
+ inOrder.verify(mMockNetd, times(1))
+ .networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(vpnRanges)));
} else {
- inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(expectedNetId),
+ inOrder.verify(mMockNetd, times(1))
+ .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(vpnRanges)));
}
@@ -9806,8 +9843,8 @@
for (int reqTypeInt : invalidReqTypeInts) {
assertThrows("Expect throws for invalid request type " + reqTypeInt,
IllegalArgumentException.class,
- () -> mService.requestNetwork(nc, reqTypeInt, null, 0, null,
- ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
+ () -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0,
+ null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
mContext.getPackageName(), getAttributionTag())
);
}
@@ -10378,6 +10415,7 @@
mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
TEST_WORK_PROFILE_APP_UID);
+ // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well.
mServiceContext.setPermission(
Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
}
@@ -10397,7 +10435,7 @@
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup)
throws Exception {
- final int testPackageNameUid = 123;
+ final int testPackageNameUid = TEST_PACKAGE_UID;
final String testPackageName = "per.app.defaults.package";
setupMultipleDefaultNetworksForOemNetworkPreferenceTest(
networkPrefToSetup, testPackageNameUid, testPackageName);
@@ -10533,6 +10571,11 @@
mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
defaultNetworkCallback.assertNoCallback();
+ final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
+ withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+ mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
+ new Handler(ConnectivityThread.getInstanceLooper())));
+
// Setup the test process to use networkPref for their default network.
setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
@@ -10543,19 +10586,22 @@
null,
mEthernetNetworkAgent.getNetwork());
- // At this point with a restricted network used, the available callback should trigger
+ // At this point with a restricted network used, the available callback should trigger.
defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
mEthernetNetworkAgent.getNetwork());
+ otherUidDefaultCallback.assertNoCallback();
// Now bring down the default network which should trigger a LOST callback.
setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
// At this point, with no network is available, the lost callback should trigger
defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+ otherUidDefaultCallback.assertNoCallback();
// Confirm we can unregister without issues.
mCm.unregisterNetworkCallback(defaultNetworkCallback);
+ mCm.unregisterNetworkCallback(otherUidDefaultCallback);
}
@Test
@@ -10573,6 +10619,11 @@
mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
defaultNetworkCallback.assertNoCallback();
+ final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
+ withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+ mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
+ new Handler(ConnectivityThread.getInstanceLooper())));
+
// Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
// The active nai for the default is null at this point as this is a restricted network.
setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
@@ -10584,15 +10635,19 @@
defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
mEthernetNetworkAgent.getNetwork());
+ otherUidDefaultCallback.assertNoCallback();
// Now bring down the default network which should trigger a LOST callback.
setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+ otherUidDefaultCallback.assertNoCallback();
// At this point, with no network is available, the lost callback should trigger
defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+ otherUidDefaultCallback.assertNoCallback();
// Confirm we can unregister without issues.
mCm.unregisterNetworkCallback(defaultNetworkCallback);
+ mCm.unregisterNetworkCallback(otherUidDefaultCallback);
}
@Test
@@ -10606,6 +10661,11 @@
mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
defaultNetworkCallback.assertNoCallback();
+ final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
+ withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+ mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
+ new Handler(ConnectivityThread.getInstanceLooper())));
+
// Setup a process different than the test process to use the default network. This means
// that the defaultNetworkCallback won't be tracked by the per-app policy.
setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref);
@@ -10621,6 +10681,9 @@
defaultNetworkCallback.assertNoCallback();
assertDefaultNetworkCapabilities(userId /* no networks */);
+ // The other UID does have access, and gets a callback.
+ otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+
// Bring up unrestricted cellular. This should now satisfy the default network.
setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
@@ -10628,25 +10691,31 @@
mEthernetNetworkAgent.getNetwork());
// At this point with an unrestricted network used, the available callback should trigger
+ // The other UID is unaffected and remains on the paid network.
defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
mCellNetworkAgent.getNetwork());
assertDefaultNetworkCapabilities(userId, mCellNetworkAgent);
+ otherUidDefaultCallback.assertNoCallback();
// Now bring down the per-app network.
setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
- // Since the callback didn't use the per-app network, no callback should fire.
+ // Since the callback didn't use the per-app network, only the other UID gets a callback.
+ // Because the preference specifies no fallback, it does not switch to cellular.
defaultNetworkCallback.assertNoCallback();
+ otherUidDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
// Now bring down the default network.
setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
// As this callback was tracking the default, this should now trigger.
defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ otherUidDefaultCallback.assertNoCallback();
// Confirm we can unregister without issues.
mCm.unregisterNetworkCallback(defaultNetworkCallback);
+ mCm.unregisterNetworkCallback(otherUidDefaultCallback);
}
/**
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8c5d1d6..97c65eb 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -22,7 +22,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -56,6 +58,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -80,6 +83,12 @@
IpConnectivityMetrics mService;
NetdEventListenerService mNetdListener;
+ final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
@Before
public void setUp() {
@@ -263,14 +272,6 @@
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
- NetworkCapabilities ncWifi = new NetworkCapabilities();
- NetworkCapabilities ncCell = new NetworkCapabilities();
- ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
- ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
- when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
- when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
ApfStats apfStats = new ApfStats.Builder()
.setDurationMs(45000)
.setReceivedRas(10)
@@ -584,11 +585,21 @@
return buffer.toString();
}
- void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
- mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
+ private void setCapabilities(int netId) {
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+ networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+ netId == 100 ? mNcWifi : mNcCell);
+ }
+
+ void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
+ setCapabilities(netId);
+ mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
}
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+ setCapabilities(netId);
mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 8ccea1a..52975e4 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -23,8 +23,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -42,6 +43,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
@@ -61,18 +63,16 @@
NetdEventListenerService mService;
ConnectivityManager mCm;
+ final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
@Before
public void setUp() {
- NetworkCapabilities ncWifi = new NetworkCapabilities();
- NetworkCapabilities ncCell = new NetworkCapabilities();
- ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
- ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
mCm = mock(ConnectivityManager.class);
- when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
- when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
mService = new NetdEventListenerService(mCm);
}
@@ -470,7 +470,16 @@
assertEquals(want, got);
}
+ private void setCapabilities(int netId) {
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+ networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+ netId == 100 ? mNcWifi : mNcCell);
+ }
+
Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
+ setCapabilities(netId);
return new Thread(() -> {
try {
mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
@@ -481,6 +490,7 @@
}
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+ setCapabilities(netId);
mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index dde77b0..2f3ee68 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -50,9 +50,7 @@
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -104,17 +102,6 @@
NetworkNotificationManager mManager;
-
- @BeforeClass
- public static void setUpClass() {
- Notification.DevFlags.sForceDefaults = true;
- }
-
- @AfterClass
- public static void tearDownClass() {
- Notification.DevFlags.sForceDefaults = false;
- }
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 7515971..516c206 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -204,7 +204,7 @@
new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback);
cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
- verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE);
+ verify(mMockStatusCallback).onStatusChanged(VCN_STATUS_CODE_ACTIVE);
cbBinder.onGatewayConnectionError(
UNDERLYING_NETWORK_CAPABILITIES,
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index a9d5822..babea36 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -19,6 +19,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -238,9 +240,14 @@
doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO))
.when(mSubMgr)
.getSubscriptionsInGroup(any());
- doReturn(isPrivileged)
+ doReturn(mTelMgr)
.when(mTelMgr)
- .hasCarrierPrivileges(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
+ .createForSubscriptionId(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
+ doReturn(isPrivileged
+ ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+ : CARRIER_PRIVILEGE_STATUS_NO_ACCESS)
+ .when(mTelMgr)
+ .checkCarrierPrivilegesForPackage(eq(TEST_PACKAGE_NAME));
}
@Test
@@ -391,7 +398,7 @@
mTestLooper.moveTimeForward(
VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
mTestLooper.dispatchAll();
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
// Verify that new instance was different, and the old one was torn down
@@ -492,7 +499,7 @@
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
try {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
fail("Expected IllegalStateException exception for system server");
} catch (IllegalStateException expected) {
}
@@ -505,7 +512,7 @@
.getBinderCallingUid();
try {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
fail("Expected security exception for non system user");
} catch (SecurityException expected) {
}
@@ -516,15 +523,24 @@
setupMockedCarrierPrivilege(false);
try {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
fail("Expected security exception for missing carrier privileges");
} catch (SecurityException expected) {
}
}
@Test
+ public void testClearVcnConfigMismatchedPackages() throws Exception {
+ try {
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, "IncorrectPackage");
+ fail("Expected security exception due to mismatched packages");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
public void testClearVcnConfig() throws Exception {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
}
@@ -535,7 +551,7 @@
mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME);
verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
}
@@ -564,7 +580,7 @@
verify(vcnInstance).updateConfig(TEST_VCN_CONFIG);
// Verify Vcn is stopped if it was already started
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
verify(vcnInstance).teardownAsynchronously();
}
@@ -781,7 +797,7 @@
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
verify(mMockPolicyListener).onPolicyChanged();
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 4fa63d4..c853fc5 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -139,8 +140,7 @@
mTestLooper.dispatchAll();
}
- @Test
- public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
+ private void verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(boolean isActive) {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]);
@@ -150,14 +150,27 @@
final TelephonySubscriptionSnapshot updatedSnapshot =
mock(TelephonySubscriptionSnapshot.class);
+ mVcn.setIsActive(isActive);
+
mVcn.updateSubscriptionSnapshot(updatedSnapshot);
mTestLooper.dispatchAll();
for (final VcnGatewayConnection gateway : gatewayConnections) {
- verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot));
+ verify(gateway, isActive ? times(1) : never())
+ .updateSubscriptionSnapshot(eq(updatedSnapshot));
}
}
+ @Test
+ public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
+ verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(true /* isActive */);
+ }
+
+ @Test
+ public void testSubscriptionSnapshotUpdatesVcnGatewayConnectionsWhileInactive() {
+ verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(false /* isActive */);
+ }
+
private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
for (final int[] caps : TEST_CAPS) {
startVcnGatewayWithCapabilities(requestListener, caps);
@@ -187,7 +200,6 @@
NetworkRequestListener requestListener,
Set<VcnGatewayConnection> expectedGatewaysTornDown) {
assertFalse(mVcn.isActive());
- assertTrue(mVcn.getVcnGatewayConnections().isEmpty());
for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) {
verify(gatewayConnection).teardownAsynchronously();
}
@@ -238,6 +250,51 @@
}
@Test
+ public void testGatewayQuitWhileInactive() {
+ final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+ final Set<VcnGatewayConnection> gatewayConnections =
+ new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
+
+ mVcn.teardownAsynchronously();
+ mTestLooper.dispatchAll();
+
+ final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
+ statusCallback.onQuit();
+ mTestLooper.dispatchAll();
+
+ // Verify that the VCN requests the networkRequests be resent
+ assertEquals(1, mVcn.getVcnGatewayConnections().size());
+ verify(mVcnNetworkProvider, never()).resendAllRequests(requestListener);
+ }
+
+ @Test
+ public void testUpdateConfigReevaluatesGatewayConnections() {
+ final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+ startGatewaysAndGetGatewayConnections(requestListener);
+ assertEquals(2, mVcn.getVcnGatewayConnectionConfigMap().size());
+
+ // Create VcnConfig with only one VcnGatewayConnectionConfig so a gateway connection is torn
+ // down
+ final VcnGatewayConnectionConfig activeConfig =
+ VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[0]);
+ final VcnGatewayConnectionConfig removedConfig =
+ VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[1]);
+ final VcnConfig updatedConfig =
+ new VcnConfig.Builder(mContext).addGatewayConnectionConfig(activeConfig).build();
+
+ mVcn.updateConfig(updatedConfig);
+ mTestLooper.dispatchAll();
+
+ final VcnGatewayConnection activeGatewayConnection =
+ mVcn.getVcnGatewayConnectionConfigMap().get(activeConfig);
+ final VcnGatewayConnection removedGatewayConnection =
+ mVcn.getVcnGatewayConnectionConfigMap().get(removedConfig);
+ verify(activeGatewayConnection, never()).teardownAsynchronously();
+ verify(removedGatewayConnection).teardownAsynchronously();
+ verify(mVcnNetworkProvider).resendAllRequests(requestListener);
+ }
+
+ @Test
public void testUpdateConfigExitsSafeMode() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections =
@@ -261,8 +318,8 @@
verify(mVcnNetworkProvider, times(2)).registerListener(eq(requestListener));
assertTrue(mVcn.isActive());
for (final int[] caps : TEST_CAPS) {
- // Expect each gateway connection created on initial startup, and again with new configs
- verify(mDeps, times(2))
+ // Expect each gateway connection created only on initial startup
+ verify(mDeps)
.newVcnGatewayConnection(
eq(mVcnContext),
eq(TEST_SUB_GROUP),
@@ -271,4 +328,14 @@
any());
}
}
+
+ @Test
+ public void testIgnoreNetworkRequestWhileInactive() {
+ mVcn.setIsActive(false /* isActive */);
+
+ final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+ triggerVcnRequestListeners(requestListener);
+
+ verify(mDeps, never()).newVcnGatewayConnection(any(), any(), any(), any(), any());
+ }
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index c64f4bc..da0571ba 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -101,6 +101,7 @@
// Cached wificond binder handlers.
private IWificond mWificond;
+ private WificondEventHandler mWificondEventHandler = new WificondEventHandler();
private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
@@ -114,6 +115,18 @@
private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
/**
+ * Interface used to listen country code event
+ */
+ public interface CountryCodeChangeListener {
+ /**
+ * Called when country code changed.
+ *
+ * @param countryCode A new country code which is 2-Character alphanumeric.
+ */
+ void onChanged(@NonNull String countryCode);
+ }
+
+ /**
* Interface used when waiting for scans to be completed (with results).
*/
public interface ScanEventCallback {
@@ -147,6 +160,46 @@
void onPnoRequestFailed();
}
+ /** @hide */
+ @VisibleForTesting
+ public class WificondEventHandler extends IWificondEventCallback.Stub {
+ private Map<CountryCodeChangeListener, Executor> mCountryCodeChangeListenerHolder =
+ new HashMap<>();
+
+ /**
+ * Register CountryCodeChangeListener with pid.
+ *
+ * @param executor The Executor on which to execute the callbacks.
+ * @param listener listener for country code changed events.
+ */
+ public void registerCountryCodeChangeListener(Executor executor,
+ CountryCodeChangeListener listener) {
+ mCountryCodeChangeListenerHolder.put(listener, executor);
+ }
+
+ /**
+ * Unregister CountryCodeChangeListener with pid.
+ *
+ * @param listener listener which registered country code changed events.
+ */
+ public void unregisterCountryCodeChangeListener(CountryCodeChangeListener listener) {
+ mCountryCodeChangeListenerHolder.remove(listener);
+ }
+
+ @Override
+ public void OnRegDomainChanged(String countryCode) {
+ Log.d(TAG, "OnRegDomainChanged " + countryCode);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCountryCodeChangeListenerHolder.forEach((listener, executor) -> {
+ executor.execute(() -> listener.onChanged(countryCode));
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
private class ScanEventHandler extends IScanEvent.Stub {
private Executor mExecutor;
private ScanEventCallback mCallback;
@@ -347,6 +400,12 @@
mWificond = wificond;
}
+ /** @hide */
+ @VisibleForTesting
+ public WificondEventHandler getWificondEventHandler() {
+ return mWificondEventHandler;
+ }
+
private class PnoScanEventHandler extends IPnoScanEvent.Stub {
private Executor mExecutor;
private ScanEventCallback mCallback;
@@ -574,6 +633,7 @@
}
try {
mWificond.asBinder().linkToDeath(() -> binderDied(), 0);
+ mWificond.registerWificondEventCallback(mWificondEventHandler);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register death notification for wificond");
// The remote has already died.
@@ -1174,6 +1234,34 @@
}
/**
+ * Register the provided listener for country code event.
+ *
+ * @param executor The Executor on which to execute the callbacks.
+ * @param listener listener for country code changed events.
+ * @return true on success, false on failure.
+ */
+ public boolean registerCountryCodeChangeListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull CountryCodeChangeListener listener) {
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+ Log.d(TAG, "registerCountryCodeEventListener called");
+ mWificondEventHandler.registerCountryCodeChangeListener(executor, listener);
+ return true;
+ }
+
+
+ /**
+ * Unregister CountryCodeChangeListener with pid.
+ *
+ * @param listener listener which registered country code changed events.
+ */
+ public void unregisterCountryCodeChangeListener(@NonNull CountryCodeChangeListener listener) {
+ Log.d(TAG, "unregisterCountryCodeEventListener called");
+ mWificondEventHandler.unregisterCountryCodeChangeListener(listener);
+ }
+
+ /**
* Register the provided callback handler for SoftAp events. The interface must first be created
* using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
* the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 4b03a49..98a0042 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -97,14 +98,20 @@
@Mock
private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
@Mock
+ private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener;
+ @Mock
+ private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener2;
+ @Mock
private Context mContext;
private TestLooper mLooper;
private TestAlarmManager mTestAlarmManager;
private AlarmManager mAlarmManager;
private WifiNl80211Manager mWificondControl;
+ private WifiNl80211Manager.WificondEventHandler mWificondEventHandler;
private static final String TEST_INTERFACE_NAME = "test_wlan_if";
private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+ private static final String TEST_COUNTRY_CODE = "US";
private static final byte[] TEST_SSID =
new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
private static final byte[] TEST_PSK =
@@ -182,6 +189,7 @@
when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+ mWificondEventHandler = mWificondControl.getWificondEventHandler();
assertEquals(true,
mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
mNormalScanCallback, mPnoScanCallback));
@@ -760,6 +768,28 @@
}
/**
+ * Ensures callback works after register CountryCodeChangeListener.
+ */
+ @Test
+ public void testCountryCodeChangeListenerInvocation() throws Exception {
+ assertTrue(mWificondControl.registerCountryCodeChangeListener(
+ Runnable::run, mCountryCodeChangeListener));
+ assertTrue(mWificondControl.registerCountryCodeChangeListener(
+ Runnable::run, mCountryCodeChangeListener2));
+
+ mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener2).onChanged(TEST_COUNTRY_CODE);
+
+ reset(mCountryCodeChangeListener);
+ reset(mCountryCodeChangeListener2);
+ mWificondControl.unregisterCountryCodeChangeListener(mCountryCodeChangeListener2);
+ mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener2, never()).onChanged(TEST_COUNTRY_CODE);
+ }
+
+ /**
* Verifies registration and invocation of wificond death handler.
*/
@Test