Merge changes from topic "nearby-bluetooth-permission-group" into sc-dev
* changes:
Request new Bluetooth runtime permissions.
Default grants for "Nearby devices" permission.
Add BLUETOOTH_SCAN and BLUETOOTH_CONNECT app ops
Split new NEARBY_DEVICES permissions
Define new NEARBY_DEVICES permission group
diff --git a/Android.bp b/Android.bp
index cbc6117..f47ee20 100644
--- a/Android.bp
+++ b/Android.bp
@@ -401,6 +401,13 @@
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.hardware.security.keymint-V1-java-source",
+ ":android.hardware.security.secureclock-V1-java-source",
+ ":android.security.apc-java-source",
+ ":android.security.authorization-java-source",
+ ":android.security.maintenance-java-source",
+ ":android.security.vpnprofilestore-java-source",
+ ":android.system.keystore2-V1-java-source",
":credstore_aidl",
":dumpstate_aidl",
":framework_native_aidl",
@@ -582,11 +589,6 @@
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.vibrator-V2-java",
- "android.security.apc-java",
- "android.security.authorization-java",
- "android.security.maintenance-java",
- "android.security.vpnprofilestore-java",
- "android.system.keystore2-V1-java",
"android.system.suspend.control.internal-java",
"devicepolicyprotosnano",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
index 999860f..e65abcf 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
@@ -48,7 +48,8 @@
@Override
public boolean onStopJob(final JobParameters params) {
Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId()
- + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason()));
+ + ", reason="
+ + JobParameters.getLegacyReasonCodeDescription(params.getLegacyStopReason()));
return false;
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 4c8ab93..baec0c3 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) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 0d3e001..60f6475 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -16,10 +16,14 @@
package android.app.job;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
+import android.content.pm.PackageManager;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.Uri;
@@ -30,6 +34,9 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Contains the parameters used to configure/identify your job. You do not create this object
* yourself, instead it is handed in to your application by the System.
@@ -82,7 +89,7 @@
*/
// TODO(142420609): make it @SystemApi for mainline
@NonNull
- public static String getReasonCodeDescription(int reasonCode) {
+ public static String getLegacyReasonCodeDescription(int reasonCode) {
switch (reasonCode) {
case REASON_CANCELED: return "canceled";
case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
@@ -96,12 +103,119 @@
}
/** @hide */
- // @SystemApi TODO make it a system api for mainline
+ // TODO: move current users of legacy reasons to new public reasons
@NonNull
public static int[] getJobStopReasonCodes() {
return JOB_STOP_REASON_CODES;
}
+ /**
+ * There is no reason the job is stopped. This is the value returned from the JobParameters
+ * object passed to {@link JobService#onStartJob(JobParameters)}.
+ */
+ public static final int STOP_REASON_UNDEFINED = 0;
+ /**
+ * The job was cancelled directly by the app, either by calling
+ * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a
+ * new job with the same job ID.
+ */
+ public static final int STOP_REASON_CANCELLED_BY_APP = 1;
+ /** The job was stopped to run a higher priority job of the app. */
+ public static final int STOP_REASON_PREEMPT = 2;
+ /**
+ * The job used up its maximum execution time and timed out. Each individual job has a maximum
+ * execution time limit, regardless of how much total quota the app has. See the note on
+ * {@link JobScheduler} for the execution time limits.
+ */
+ public static final int STOP_REASON_TIMEOUT = 3;
+ /**
+ * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this
+ * job.
+ */
+ public static final int STOP_REASON_DEVICE_STATE = 4;
+ /**
+ * The requested battery-not-low constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5;
+ /**
+ * The requested charging constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresCharging(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_CHARGING = 6;
+ /**
+ * The requested connectivity constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7;
+ /**
+ * The requested idle constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+ /**
+ * The requested storage-not-low constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9;
+ /**
+ * The app has consumed all of its current quota. Each app is assigned a quota of how much
+ * it can run jobs within a certain time frame. The quota is informed, in part, by app standby
+ * buckets. Once an app has used up all of its quota, it won't be able to start jobs until
+ * quota is replenished, is changed, or is temporarily not applied.
+ *
+ * @see UsageStatsManager#getAppStandbyBucket()
+ */
+ public static final int STOP_REASON_QUOTA = 10;
+ /**
+ * The app is restricted from running in the background.
+ *
+ * @see ActivityManager#isBackgroundRestricted()
+ * @see PackageManager#isInstantApp()
+ */
+ public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11;
+ /**
+ * The current standby bucket requires that the job stop now.
+ *
+ * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+ */
+ public static final int STOP_REASON_APP_STANDBY = 12;
+ /**
+ * The user stopped the job. This can happen either through force-stop, or via adb shell
+ * commands.
+ */
+ public static final int STOP_REASON_USER = 13;
+ /** The system is doing some processing that requires stopping this job. */
+ public static final int STOP_REASON_SYSTEM_PROCESSING = 14;
+
+ /** @hide */
+ @IntDef(prefix = {"STOP_REASON_"}, value = {
+ STOP_REASON_UNDEFINED,
+ STOP_REASON_CANCELLED_BY_APP,
+ STOP_REASON_PREEMPT,
+ STOP_REASON_TIMEOUT,
+ STOP_REASON_DEVICE_STATE,
+ STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+ STOP_REASON_CONSTRAINT_CHARGING,
+ STOP_REASON_CONSTRAINT_CONNECTIVITY,
+ STOP_REASON_CONSTRAINT_DEVICE_IDLE,
+ STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+ STOP_REASON_QUOTA,
+ STOP_REASON_BACKGROUND_RESTRICTION,
+ STOP_REASON_APP_STANDBY,
+ STOP_REASON_USER,
+ STOP_REASON_SYSTEM_PROCESSING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StopReason {
+ }
+
@UnsupportedAppUsage
private final int jobId;
private final PersistableBundle extras;
@@ -116,7 +230,8 @@
private final String[] mTriggeredContentAuthorities;
private final Network network;
- private int stopReason; // Default value of stopReason is REASON_CANCELED
+ private int mStopReason = STOP_REASON_UNDEFINED;
+ private int mLegacyStopReason; // Default value of stopReason is REASON_CANCELED
private String debugStopReason; // Human readable stop reason for debugging.
/** @hide */
@@ -145,15 +260,23 @@
}
/**
- * Reason onStopJob() was called on this job.
- * @hide
+ * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
+ * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
+ * yet been called.
*/
+ @StopReason
public int getStopReason() {
- return stopReason;
+ return mStopReason;
+ }
+
+ /** @hide */
+ public int getLegacyStopReason() {
+ return mLegacyStopReason;
}
/**
* Reason onStopJob() was called on this job.
+ *
* @hide
*/
public String getDebugStopReason() {
@@ -368,13 +491,16 @@
} else {
network = null;
}
- stopReason = in.readInt();
+ mStopReason = in.readInt();
+ mLegacyStopReason = in.readInt();
debugStopReason = in.readString();
}
/** @hide */
- public void setStopReason(int reason, String debugStopReason) {
- stopReason = reason;
+ public void setStopReason(@StopReason int reason, int legacyStopReason,
+ String debugStopReason) {
+ mStopReason = reason;
+ mLegacyStopReason = legacyStopReason;
this.debugStopReason = debugStopReason;
}
@@ -406,7 +532,8 @@
} else {
dest.writeInt(0);
}
- dest.writeInt(stopReason);
+ dest.writeInt(mStopReason);
+ dest.writeInt(mLegacyStopReason);
dest.writeString(debugStopReason);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 0f3d299..fa7a2d3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -139,19 +139,23 @@
* Once this method is called, you no longer need to call
* {@link #jobFinished(JobParameters, boolean)}.
*
- * <p>This will happen if the requirements specified at schedule time are no longer met. For
+ * <p>This may happen if the requirements specified at schedule time are no longer met. For
* example you may have requested WiFi with
* {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
* job was executing the user toggled WiFi. Another example is if you had specified
- * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
- * idle maintenance window. You are solely responsible for the behavior of your application
- * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+ * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left
+ * its idle maintenance window. There are many other reasons a job can be stopped early besides
+ * constraints no longer being satisfied. {@link JobParameters#getStopReason()} will return the
+ * reason this method was called. You are solely responsible for the behavior of your
+ * application upon receipt of this message; your app will likely start to misbehave if you
+ * ignore it.
* <p>
* Once this method returns (or times out), the system releases the wakelock that it is holding
* on behalf of the job.</p>
*
- * @param params The parameters identifying this job, as supplied to
- * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param params The parameters identifying this job, similar to what was supplied to the job in
+ * the {@link #onStartJob(JobParameters)} callback, but with the stop reason
+ * included.
* @return {@code true} to indicate to the JobManager whether you'd like to reschedule
* this job based on the retry criteria provided at job creation-time; or {@code false}
* to end the job entirely. Regardless of the value returned, your job must stop executing.
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 7833a03..6ae91a0 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.util.proto.ProtoOutputStream;
import java.util.List;
@@ -36,7 +37,7 @@
/**
* Cancel the jobs for a given uid (e.g. when app data is cleared)
*/
- void cancelJobsForUid(int uid, String reason);
+ void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, String debugReason);
/**
* These are for activity manager to communicate to use what is currently performing backups.
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 b958c3f..325be1b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -266,6 +266,8 @@
String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];
+ int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
+
String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];
private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
@@ -505,6 +507,7 @@
int[] preferredUidForContext = mRecycledPreferredUidForContext;
int[] workTypeForContext = mRecycledWorkTypeForContext;
String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
+ int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext;
String[] shouldStopJobReason = mRecycledShouldStopJobReason;
updateCounterConfigLocked();
@@ -528,6 +531,7 @@
slotChanged[i] = false;
preferredUidForContext[i] = js.getPreferredUid();
preemptReasonForContext[i] = null;
+ preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED;
shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
}
if (DEBUG) {
@@ -551,6 +555,7 @@
int allWorkTypes = getJobWorkTypes(nextPending);
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
boolean startingJob = false;
+ int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
String preemptReason = null;
// TODO(141645789): rewrite this to look at empty contexts first so we don't
// unnecessarily preempt
@@ -582,6 +587,7 @@
// assign the new job to this context since we'll reassign when the
// preempted job finally stops.
preemptReason = reason;
+ preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
}
continue;
}
@@ -597,6 +603,7 @@
minPriorityForPreemption = jobPriority;
selectedContextId = j;
preemptReason = "higher priority job found";
+ preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
// In this case, we're just going to preempt a low priority job, we're not
// actually starting a job, so don't set startingJob.
}
@@ -604,6 +611,7 @@
if (selectedContextId != -1) {
contextIdToJobMap[selectedContextId] = nextPending;
slotChanged[selectedContextId] = true;
+ preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
preemptReasonForContext[selectedContextId] = preemptReason;
}
if (startingJob) {
@@ -631,8 +639,13 @@
}
// preferredUid will be set to uid of currently running job.
activeServices.get(i).cancelExecutingJobLocked(
+ preemptReasonCodeForContext[i],
JobParameters.REASON_PREEMPT, preemptReasonForContext[i]);
- preservePreferredUid = true;
+ // Only preserve the UID if we're preempting for the same UID. If we're stopping
+ // the job because something is pending (eg. EJs), then we shouldn't preserve
+ // the UID.
+ preservePreferredUid =
+ preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT;
} else {
final JobStatus pendingJob = contextIdToJobMap[i];
if (DEBUG) {
@@ -657,7 +670,8 @@
final JobStatus jobStatus = jsc.getRunningJobLocked();
if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_TIMEOUT, debugReason);
+ jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_TIMEOUT, debugReason);
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
index 6ffac91..02f9129 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
@@ -374,7 +374,7 @@
pw.print(pe.stopReasons.valueAt(k));
pw.print("x ");
pw.print(JobParameters
- .getReasonCodeDescription(pe.stopReasons.keyAt(k)));
+ .getLegacyReasonCodeDescription(pe.stopReasons.keyAt(k)));
}
pw.println();
}
@@ -621,7 +621,7 @@
if (reason != null) {
pw.print(mEventReasons[index]);
} else {
- pw.print(JobParameters.getReasonCodeDescription(
+ pw.print(JobParameters.getLegacyReasonCodeDescription(
(mEventCmds[index] & EVENT_STOP_REASON_MASK)
>> EVENT_STOP_REASON_SHIFT));
}
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 2b08ba5..a041f8c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -746,8 +746,11 @@
Slog.d(TAG, "Removing jobs for package " + pkgName
+ " in user " + userId);
}
+ // By the time we get here, the process should have already
+ // been stopped, so the app wouldn't get the stop reason,
+ // so just put USER instead of UNINSTALL or DISABLED.
cancelJobsForPackageAndUid(pkgName, pkgUid,
- "app disabled");
+ JobParameters.STOP_REASON_USER, "app disabled");
}
} catch (RemoteException|IllegalArgumentException e) {
/*
@@ -785,7 +788,11 @@
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
- cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
+ // By the time we get here, the process should have already
+ // been stopped, so the app wouldn't get the stop reason,
+ // so just put USER instead of UNINSTALL or DISABLED.
+ cancelJobsForPackageAndUid(pkgName, uidRemoved,
+ JobParameters.STOP_REASON_USER, "app uninstalled");
synchronized (mLock) {
for (int c = 0; c < mControllers.size(); ++c) {
mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
@@ -837,7 +844,8 @@
if (DEBUG) {
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
- cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
+ cancelJobsForPackageAndUid(pkgName, pkgUid,
+ JobParameters.STOP_REASON_USER, "app force stopped");
}
}
}
@@ -924,8 +932,7 @@
final String servicePkg = job.getService().getPackageName();
if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
// Only limit schedule calls for persisted jobs scheduled by the app itself.
- final String pkg =
- packageName == null ? job.getService().getPackageName() : packageName;
+ final String pkg = packageName == null ? servicePkg : packageName;
if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) {
// Don't log too frequently
@@ -972,14 +979,10 @@
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(uId,
- job.getService().getPackageName())) {
- Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
- + " -- package not allowed to start");
- return JobScheduler.RESULT_FAILURE;
- }
- } catch (RemoteException e) {
+ if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
+ Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ + " -- package not allowed to start");
+ return JobScheduler.RESULT_FAILURE;
}
synchronized (mLock) {
@@ -1029,7 +1032,8 @@
if (toCancel != null) {
// Implicitly replaces the existing job record with the new instance
- cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
+ cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
+ "job rescheduled by app");
} else {
startTrackingJobLocked(jobStatus, null);
}
@@ -1105,7 +1109,10 @@
final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
for (int i=0; i<jobsForUser.size(); i++) {
JobStatus toRemove = jobsForUser.get(i);
- cancelJobImplLocked(toRemove, null, "user removed");
+ // By the time we get here, the process should have already been stopped, so the
+ // app wouldn't get the stop reason, so just put USER instead of UNINSTALL.
+ cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
+ "user removed");
}
}
}
@@ -1117,7 +1124,8 @@
}
}
- void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
+ void cancelJobsForPackageAndUid(String pkgName, int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
if ("android".equals(pkgName)) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
return;
@@ -1127,7 +1135,7 @@
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
if (job.getSourcePackageName().equals(pkgName)) {
- cancelJobImplLocked(job, null, reason);
+ cancelJobImplLocked(job, null, reason, debugReason);
}
}
}
@@ -1137,10 +1145,11 @@
* Entry point from client to cancel all jobs originating from their uid.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
- * @param uid Uid to check against for removal of a job.
*
+ * @param uid Uid to check against for removal of a job.
*/
- public boolean cancelJobsForUid(int uid, String reason) {
+ public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return false;
@@ -1151,7 +1160,7 @@
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
- cancelJobImplLocked(toRemove, null, reason);
+ cancelJobImplLocked(toRemove, null, reason, debugReason);
jobsCanceled = true;
}
}
@@ -1162,15 +1171,17 @@
* Entry point from client to cancel the job corresponding to the jobId provided.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
- * @param uid Uid of the calling client.
+ *
+ * @param uid Uid of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
- public boolean cancelJob(int uid, int jobId, int callingUid) {
+ private boolean cancelJob(int uid, int jobId, int callingUid,
+ @JobParameters.StopReason int reason) {
JobStatus toCancel;
synchronized (mLock) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
- cancelJobImplLocked(toCancel, null,
+ cancelJobImplLocked(toCancel, null, reason,
"cancel() called by app, callingUid=" + callingUid
+ " uid=" + uid + " jobId=" + jobId);
}
@@ -1184,7 +1195,8 @@
* {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
* currently scheduled jobs.
*/
- private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
+ private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
+ @JobParameters.StopReason int reason, String debugReason) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked();
stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
@@ -1193,7 +1205,8 @@
mJobPackageTracker.noteNonpending(cancelled);
}
// Cancel if running.
- stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
+ stopJobOnServiceContextLocked(cancelled, reason, JobParameters.REASON_CANCELED,
+ debugReason);
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
@@ -1232,7 +1245,8 @@
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null && !executing.canRunInDoze()) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
+ jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_DEVICE_IDLE,
"cancelled due to doze");
}
}
@@ -1430,7 +1444,8 @@
if (DEBUG) {
Slog.v(TAG, " replacing " + oldJob + " with " + newJob);
}
- cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation");
+ cancelJobImplLocked(oldJob, newJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+ "deferred rtc calculation");
}
}
};
@@ -1550,12 +1565,13 @@
return removed;
}
- private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
+ private boolean stopJobOnServiceContextLocked(JobStatus job,
+ @JobParameters.StopReason int reason, int legacyReason, String debugReason) {
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
- jsc.cancelExecutingJobLocked(reason, debugReason);
+ jsc.cancelExecutingJobLocked(reason, legacyReason, debugReason);
return true;
}
}
@@ -1880,7 +1896,7 @@
queueReadyJobsForExecutionLocked();
break;
case MSG_STOP_JOB:
- cancelJobImplLocked((JobStatus) message.obj, null,
+ cancelJobImplLocked((JobStatus) message.obj, null, message.arg1,
"app no longer allowed to run");
break;
@@ -1895,7 +1911,9 @@
final boolean disabled = message.arg2 != 0;
updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (disabled) {
- cancelJobsForUid(uid, "uid gone");
+ cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ "uid gone");
}
synchronized (mLock) {
mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1913,7 +1931,9 @@
final int uid = message.arg1;
final boolean disabled = message.arg2 != 0;
if (disabled) {
- cancelJobsForUid(uid, "app uid idle");
+ cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ "app uid idle");
}
synchronized (mLock) {
mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1965,10 +1985,12 @@
if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX
&& !running.areDynamicConstraintsSatisfied()) {
serviceContext.cancelExecutingJobLocked(
+ running.getStopReason(),
JobParameters.REASON_RESTRICTED_BUCKET,
"cancelled due to restricted bucket");
} else {
serviceContext.cancelExecutingJobLocked(
+ running.getStopReason(),
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
"cancelled due to unsatisfied constraints");
}
@@ -1977,7 +1999,9 @@
if (restriction != null) {
final int reason = restriction.getReason();
serviceContext.cancelExecutingJobLocked(reason,
- "restricted due to " + JobParameters.getReasonCodeDescription(reason));
+ restriction.getLegacyReason(),
+ "restricted due to " + JobParameters.getLegacyReasonCodeDescription(
+ reason));
}
}
}
@@ -2058,15 +2082,14 @@
@Override
public void accept(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
- job.getJob().getService().getPackageName())) {
- Slog.w(TAG, "Aborting job " + job.getUid() + ":"
- + job.getJob().toString() + " -- package not allowed to start");
- mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
- return;
- }
- } catch (RemoteException e) {
+ if (mActivityManagerInternal.isAppStartModeDisabled(job.getUid(),
+ job.getJob().getService().getPackageName())) {
+ Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ + job.getJob().toString() + " -- package not allowed to start");
+ mHandler.obtainMessage(MSG_STOP_JOB,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job)
+ .sendToTarget();
+ return;
}
final boolean shouldForceBatchJob;
@@ -2276,7 +2299,7 @@
if (restriction != null) {
if (DEBUG) {
Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
- + " restricted due to " + restriction.getReason());
+ + " restricted due to " + restriction.getLegacyReason());
}
return false;
}
@@ -2367,8 +2390,9 @@
}
@Override
- public void cancelJobsForUid(int uid, String reason) {
- JobSchedulerService.this.cancelJobsForUid(uid, reason);
+ public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
+ JobSchedulerService.this.cancelJobsForUid(uid, reason, debugReason);
}
@Override
@@ -2706,6 +2730,7 @@
final long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP,
"cancelAll() called by app, callingUid=" + uid);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2718,7 +2743,8 @@
final long ident = Binder.clearCallingIdentity();
try {
- JobSchedulerService.this.cancelJob(uid, jobId, uid);
+ JobSchedulerService.this.cancelJob(uid, jobId, uid,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2924,12 +2950,13 @@
if (!hasJobId) {
pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
- if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+ if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER,
+ "cancel shell command for package")) {
pw.println("No matching jobs found.");
}
} else {
pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
- if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
+ if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
pw.println("No matching job found.");
}
}
@@ -3164,8 +3191,9 @@
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
final JobRestriction restriction = mJobRestrictions.get(i);
if (restriction.isJobRestricted(job)) {
- final int reason = restriction.getReason();
- pw.print(" " + JobParameters.getReasonCodeDescription(reason));
+ final int reason = restriction.getLegacyReason();
+ pw.print(" ");
+ pw.print(JobParameters.getLegacyReasonCodeDescription(reason));
}
}
} else {
@@ -3430,7 +3458,7 @@
final long restrictionsToken = proto.start(
JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS);
proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON,
- restriction.getReason());
+ restriction.getLegacyReason());
proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING,
restriction.isJobRestricted(job));
proto.end(restrictionsToken);
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 9ef46df..e8bcbfb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -354,8 +354,9 @@
/** Called externally when a job that was scheduled for execution should be cancelled. */
@GuardedBy("mLock")
- void cancelExecutingJobLocked(int reason, @NonNull String debugReason) {
- doCancelLocked(reason, debugReason);
+ void cancelExecutingJobLocked(@JobParameters.StopReason int reason,
+ int legacyStopReason, @NonNull String debugReason) {
+ doCancelLocked(reason, legacyStopReason, debugReason);
}
int getPreferredUid() {
@@ -387,7 +388,8 @@
&& (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
&& (!matchJobId || jobId == executing.getJobId())) {
if (mVerb == VERB_EXECUTING) {
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+ mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.REASON_TIMEOUT, reason);
sendStopMessageLocked("force timeout from shell");
return true;
}
@@ -614,7 +616,8 @@
}
@GuardedBy("mLock")
- private void doCancelLocked(int stopReasonCode, @Nullable String debugReason) {
+ private void doCancelLocked(@JobParameters.StopReason int stopReasonCode, int legacyStopReason,
+ @Nullable String debugReason) {
if (mVerb == VERB_FINISHED) {
if (DEBUG) {
Slog.d(TAG,
@@ -622,8 +625,8 @@
}
return;
}
- mParams.setStopReason(stopReasonCode, debugReason);
- if (stopReasonCode == JobParameters.REASON_PREEMPT) {
+ mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason);
+ if (legacyStopReason == JobParameters.REASON_PREEMPT) {
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
NO_PREFERRED_UID;
}
@@ -781,7 +784,8 @@
// Not an error - client ran out of time.
Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
+ " Sending onStop: " + getRunningJobNameLocked());
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
+ mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.REASON_TIMEOUT, "client timed out");
sendStopMessageLocked("timeout while executing");
} else {
// We've given the app the minimum execution time. See if we should stop it or
@@ -790,7 +794,11 @@
if (reason != null) {
Slog.i(TAG, "Stopping client after min execution time: "
+ getRunningJobNameLocked() + " because " + reason);
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+ // Tell the developer we're stopping the job due to device state instead
+ // of timeout since all of the reasons could equate to "the system needs
+ // the resources the app is currently using."
+ mParams.setStopReason(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_TIMEOUT, reason);
sendStopMessageLocked(reason);
} else {
Slog.i(TAG, "Letting " + getRunningJobNameLocked()
@@ -844,12 +852,12 @@
}
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
- final int stopReason = mParams.getStopReason();
- mJobPackageTracker.noteInactive(completedJob, stopReason, reason);
+ final int legacyStopReason = mParams.getLegacyStopReason();
+ mJobPackageTracker.noteInactive(completedJob, legacyStopReason, reason);
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.getSourceUid(), null, completedJob.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
- stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+ legacyStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
completedJob.hasChargingConstraint(),
completedJob.hasBatteryNotLowConstraint(),
completedJob.hasStorageNotLowConstraint(),
@@ -860,7 +868,7 @@
completedJob.hasContentTriggerConstraint());
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
- stopReason);
+ legacyStopReason);
} catch (RemoteException e) {
// Whatever.
}
@@ -879,7 +887,7 @@
service = null;
mAvailable = true;
removeOpTimeOutLocked();
- mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule);
+ mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
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/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index a230b23..548a1ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -210,7 +210,8 @@
jobStatus.maybeLogBucketMismatch();
}
boolean didChange =
- jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun);
+ jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun,
+ !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
didChange |= jobStatus.setUidActive(isActive);
return didChange;
}
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..6825cf2 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 {
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 bad8dc1..8fa0d3e 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
@@ -24,11 +24,13 @@
import android.app.AppGlobals;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.app.job.JobWorkItem;
import android.content.ClipData;
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;
@@ -37,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;
@@ -54,6 +57,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.function.Predicate;
/**
@@ -353,6 +357,9 @@
*/
private long mLastFailedRunTime;
+ /** Whether or not the app is background restricted by the user (FAS). */
+ private boolean mIsUserBgRestricted;
+
/**
* Transient: when a job is inflated from disk before we have a reliable RTC clock time,
* we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
@@ -409,6 +416,9 @@
/** The job's dynamic requirements have been satisfied. */
private boolean mReadyDynamicSatisfied;
+ /** The reason a job most recently went from ready to not ready. */
+ private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return callingUid;
@@ -520,8 +530,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());
@@ -1042,6 +1059,11 @@
mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
}
+ @JobParameters.StopReason
+ public int getStopReason() {
+ return mReasonReadyToUnready;
+ }
+
/**
* Return the fractional position of "now" within the "run time" window of
* this job.
@@ -1172,7 +1194,9 @@
}
/** @return true if the constraint was changed, false otherwise. */
- boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state) {
+ boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state,
+ boolean isUserBgRestricted) {
+ mIsUserBgRestricted = isUserBgRestricted;
if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, nowElapsed, state)) {
// The constraint was changed. Update the ready flag.
mReadyNotRestrictedInBg = state;
@@ -1226,6 +1250,7 @@
"Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for "
+ toShortString());
}
+ final boolean wasReady = !state && isReady();
satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
mReadyDynamicSatisfied = mDynamicConstraints != 0
@@ -1244,9 +1269,81 @@
mConstraintChangeHistoryIndex =
(mConstraintChangeHistoryIndex + 1) % NUM_CONSTRAINT_CHANGE_HISTORY;
+ // Can't use isReady() directly since "cache booleans" haven't updated yet.
+ final boolean isReady = readinessStatusWithConstraint(constraint, state);
+ if (wasReady && !isReady) {
+ mReasonReadyToUnready = constraintToStopReason(constraint);
+ } else if (!wasReady && isReady) {
+ mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+ }
+
return true;
}
+ @JobParameters.StopReason
+ private int constraintToStopReason(int constraint) {
+ switch (constraint) {
+ case CONSTRAINT_BATTERY_NOT_LOW:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_CHARGING:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_CHARGING;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_CONNECTIVITY:
+ return JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY;
+ case CONSTRAINT_IDLE:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_DEVICE_IDLE;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_STORAGE_NOT_LOW:
+ return JobParameters.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+
+ case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
+ // The BACKGROUND_NOT_RESTRICTED constraint could be dissatisfied either because
+ // the app is background restricted, or because we're restricting background work
+ // in battery saver. Assume that background restriction is the reason apps that
+ // are background restricted have their jobs stopped, and battery saver otherwise.
+ // This has the benefit of being consistent for background restricted apps
+ // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+ // battery saver state.
+ if (mIsUserBgRestricted) {
+ return JobParameters.STOP_REASON_BACKGROUND_RESTRICTION;
+ }
+ return JobParameters.STOP_REASON_DEVICE_STATE;
+ case CONSTRAINT_DEVICE_NOT_DOZING:
+ return JobParameters.STOP_REASON_DEVICE_STATE;
+
+ case CONSTRAINT_WITHIN_QUOTA:
+ case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
+ return JobParameters.STOP_REASON_QUOTA;
+
+ // These should never be stop reasons since they can never go from true to false.
+ case CONSTRAINT_CONTENT_TRIGGER:
+ case CONSTRAINT_DEADLINE:
+ case CONSTRAINT_TIMING_DELAY:
+ default:
+ Slog.wtf(TAG, "Unsupported constraint (" + constraint + ") --stop reason mapping");
+ return JobParameters.STOP_REASON_UNDEFINED;
+ }
+ }
+
boolean isConstraintSatisfied(int constraint) {
return (satisfiedConstraints&constraint) != 0;
}
@@ -1330,33 +1427,42 @@
* granted, based on its requirements.
*/
boolean wouldBeReadyWithConstraint(int constraint) {
+ return readinessStatusWithConstraint(constraint, true);
+ }
+
+ private boolean readinessStatusWithConstraint(int constraint, boolean value) {
boolean oldValue = false;
int satisfied = mSatisfiedConstraintsOfInterest;
switch (constraint) {
case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
oldValue = mReadyNotRestrictedInBg;
- mReadyNotRestrictedInBg = true;
+ mReadyNotRestrictedInBg = value;
break;
case CONSTRAINT_DEADLINE:
oldValue = mReadyDeadlineSatisfied;
- mReadyDeadlineSatisfied = true;
+ mReadyDeadlineSatisfied = value;
break;
case CONSTRAINT_DEVICE_NOT_DOZING:
oldValue = mReadyNotDozing;
- mReadyNotDozing = true;
+ mReadyNotDozing = value;
break;
case CONSTRAINT_WITHIN_QUOTA:
oldValue = mReadyWithinQuota;
- mReadyWithinQuota = true;
+ mReadyWithinQuota = value;
break;
case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
oldValue = mReadyWithinExpeditedQuota;
- mReadyWithinExpeditedQuota = true;
+ mReadyWithinExpeditedQuota = value;
break;
default:
- satisfied |= constraint;
+ if (value) {
+ satisfied |= constraint;
+ } else {
+ satisfied &= ~constraint;
+ }
mReadyDynamicSatisfied = mDynamicConstraints != 0
&& mDynamicConstraints == (satisfied & mDynamicConstraints);
+
break;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index ac59f95..2962b10 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -17,6 +17,7 @@
package com.android.server.job.restrictions;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
@@ -26,9 +27,8 @@
/**
* Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
* should be scheduled or not based on the state of the system/device.
- * Every restriction is associated with exactly one reason (from {@link
- * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link
- * #getReason()}.
+ * Every restriction is associated with exactly one stop reason, which could be retrieved using
+ * {@link #getReason()} (and the legacy reason via {@link #getLegacyReason()}).
* Note, that this is not taken into account for the jobs that have priority
* {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
*/
@@ -36,10 +36,13 @@
final JobSchedulerService mService;
private final int mReason;
+ private final int mLegacyReason;
- JobRestriction(JobSchedulerService service, int reason) {
+ JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
+ int legacyReason) {
mService = service;
mReason = reason;
+ mLegacyReason = legacyReason;
}
/**
@@ -66,7 +69,12 @@
public abstract void dumpConstants(ProtoOutputStream proto);
/** @return reason code for the Restriction. */
+ @JobParameters.StopReason
public final int getReason() {
return mReason;
}
+
+ public final int getLegacyReason() {
+ return mLegacyReason;
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index 954a5b8..8b699e9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -34,7 +34,7 @@
private PowerManager mPowerManager;
public ThermalStatusRestriction(JobSchedulerService service) {
- super(service, JobParameters.REASON_DEVICE_THERMAL);
+ super(service, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.REASON_DEVICE_THERMAL);
}
@Override
diff --git a/core/api/current.txt b/core/api/current.txt
index e895813..bc74372 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5833,8 +5833,8 @@
method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.Bitmap);
method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.drawable.Icon);
- method @NonNull public android.app.Notification.BigPictureStyle bigPictureContentDescription(@Nullable CharSequence);
method @NonNull public android.app.Notification.BigPictureStyle setBigContentTitle(@Nullable CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle setContentDescription(@Nullable CharSequence);
method @NonNull public android.app.Notification.BigPictureStyle setSummaryText(@Nullable CharSequence);
method @NonNull public android.app.Notification.BigPictureStyle showBigPictureWhenCollapsed(boolean);
}
@@ -7186,6 +7186,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);
@@ -7193,7 +7194,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;
@@ -7248,6 +7248,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);
@@ -7267,7 +7268,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);
@@ -7973,6 +7973,7 @@
method @NonNull public android.os.PersistableBundle getExtras();
method public int getJobId();
method @Nullable public android.net.Network getNetwork();
+ method public int getStopReason();
method @NonNull public android.os.Bundle getTransientExtras();
method @Nullable public String[] getTriggeredContentAuthorities();
method @Nullable public android.net.Uri[] getTriggeredContentUris();
@@ -7981,6 +7982,21 @@
method public boolean isOverrideDeadlineExpired();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR;
+ field public static final int STOP_REASON_APP_STANDBY = 12; // 0xc
+ field public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; // 0xb
+ field public static final int STOP_REASON_CANCELLED_BY_APP = 1; // 0x1
+ field public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; // 0x5
+ field public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; // 0x6
+ field public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; // 0x7
+ field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+ field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
+ field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
+ field public static final int STOP_REASON_PREEMPT = 2; // 0x2
+ field public static final int STOP_REASON_QUOTA = 10; // 0xa
+ field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
+ field public static final int STOP_REASON_TIMEOUT = 3; // 0x3
+ field public static final int STOP_REASON_UNDEFINED = 0; // 0x0
+ field public static final int STOP_REASON_USER = 13; // 0xd
}
public abstract class JobScheduler {
@@ -21328,6 +21344,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);
@@ -21355,6 +21372,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
@@ -21445,6 +21463,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);
}
@@ -42131,7 +42153,7 @@
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
- method public int getDeviceToDeviceStatusSharing(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);
method @Nullable public int[] getSubscriptionIds(int);
@@ -42144,7 +42166,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 setDeviceToDeviceStatusSharing(int, 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);
method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long);
@@ -42367,6 +42389,7 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getNetworkSlicingConfiguration(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.data.SlicingConfig,android.telephony.TelephonyManager.SlicingException>);
method public String getNetworkSpecifier();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
+ method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
@@ -52509,9 +52532,43 @@
package android.view.translation {
+ public final class TranslationCapability implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+ method public int getState();
+ method public int getSupportedTranslationFlags();
+ method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+ method public boolean isUiTranslationEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationCapability> CREATOR;
+ field public static final int STATE_AVAILABLE_TO_DOWNLOAD = 1; // 0x1
+ field public static final int STATE_DOWNLOADING = 2; // 0x2
+ field public static final int STATE_ON_DEVICE = 3; // 0x3
+ }
+
+ public final class TranslationContext implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+ method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+ method public int getTranslationFlags();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationContext> CREATOR;
+ field public static final int FLAG_DICTIONARY_DESCRIPTION = 4; // 0x4
+ field public static final int FLAG_LOW_LATENCY = 1; // 0x1
+ field public static final int FLAG_TRANSLITERATION = 2; // 0x2
+ }
+
+ public static final class TranslationContext.Builder {
+ ctor public TranslationContext.Builder(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
+ method @NonNull public android.view.translation.TranslationContext build();
+ method @NonNull public android.view.translation.TranslationContext.Builder setTranslationFlags(int);
+ }
+
public final class TranslationManager {
- method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
- method @NonNull @WorkerThread public java.util.List<java.lang.String> getSupportedLocales();
+ 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 public void removeTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
}
public final class TranslationRequest implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 63d606b..a0e0f71 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -699,6 +699,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 {
@@ -1192,12 +1195,14 @@
public class RestoreSet implements android.os.Parcelable {
ctor public RestoreSet();
- ctor public RestoreSet(String, String, long);
+ ctor public RestoreSet(@Nullable String, @Nullable String, long);
+ ctor public RestoreSet(@Nullable String, @Nullable String, long, int);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.backup.RestoreSet> CREATOR;
- field public String device;
- field public String name;
+ field public final int backupTransportFlags;
+ field @Nullable public String device;
+ field @Nullable public String name;
field public long token;
}
@@ -5226,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();
@@ -8495,6 +8501,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();
@@ -8508,6 +8515,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";
@@ -8521,6 +8529,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";
}
@@ -10258,9 +10267,10 @@
ctor public TranslationService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onConnected();
- method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, int);
+ method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationContext, int);
method public void onDisconnected();
method public abstract void onFinishTranslationSession(int);
+ method public abstract void onTranslationCapabilitiesRequest(int, int, @NonNull java.util.function.Consumer<java.util.Set<android.view.translation.TranslationCapability>>);
method public abstract void onTranslationRequest(@NonNull android.view.translation.TranslationRequest, int, @NonNull android.os.CancellationSignal, @NonNull android.service.translation.TranslationService.OnTranslationResultCallback);
field public static final String SERVICE_INTERFACE = "android.service.translation.TranslationService";
field public static final String SERVICE_META_DATA = "android.translation_service";
@@ -13292,6 +13302,7 @@
method @Nullable public android.telephony.ims.RcsContactPresenceTuple getCapabilityTuple(@NonNull String);
method @NonNull public java.util.List<android.telephony.ims.RcsContactPresenceTuple> getCapabilityTuples();
method @NonNull public android.net.Uri getContactUri();
+ method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
method public int getRequestResult();
method public int getSourceType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -13306,6 +13317,14 @@
field public static final int SOURCE_TYPE_NETWORK = 0; // 0x0
}
+ public static final class RcsContactUceCapability.OptionsBuilder {
+ ctor public RcsContactUceCapability.OptionsBuilder(@NonNull android.net.Uri);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTag(@NonNull String);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTags(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder setRequestResult(int);
+ }
+
public static final class RcsContactUceCapability.PresenceBuilder {
ctor public RcsContactUceCapability.PresenceBuilder(@NonNull android.net.Uri, int, int);
method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuple(@NonNull android.telephony.ims.RcsContactPresenceTuple);
@@ -13599,7 +13618,7 @@
package android.telephony.ims.stub {
public interface CapabilityExchangeEventListener {
- method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
+ method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
method public void onUnpublish() throws android.telephony.ims.ImsException;
}
@@ -13796,7 +13815,7 @@
public class RcsCapabilityExchangeImplBase {
ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
- method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
+ method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
method public void subscribeForCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3
field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1
@@ -14256,6 +14275,10 @@
package android.view.translation {
+ public final class TranslationCapability implements android.os.Parcelable {
+ ctor public TranslationCapability(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, boolean, int);
+ }
+
public final class UiTranslationManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ae1cbf7..7bc1993 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -817,6 +817,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();
@@ -1711,35 +1712,16 @@
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 class VintfObject {
@@ -1856,6 +1838,63 @@
}
+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 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 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 {
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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 3bfddf7..e5a969a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -244,6 +244,8 @@
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
boolean isAssistDataAllowedOnCurrentActivity();
+ boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
+ in String callingPackageName);
/**
* Notify the system that the keyguard is going away.
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 3a533c9..4eda6fe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -20,8 +20,6 @@
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
import static java.util.Objects.requireNonNull;
import android.annotation.AttrRes;
@@ -1216,7 +1214,7 @@
/**
* {@link #extras} key: this is a content description of the big picture supplied from
* {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
- * {@link BigPictureStyle#bigPictureContentDescription(CharSequence)}.
+ * {@link BigPictureStyle#setContentDescription(CharSequence)}.
*/
public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
"android.pictureContentDescription";
@@ -3701,8 +3699,6 @@
private int mTextColorsAreForBackground = COLOR_INVALID;
private int mPrimaryTextColor = COLOR_INVALID;
private int mSecondaryTextColor = COLOR_INVALID;
- private int mBackgroundColor = COLOR_INVALID;
- private int mForegroundColor = COLOR_INVALID;
private boolean mRebuildStyledRemoteViews;
private boolean mTintActionButtons;
@@ -5041,7 +5037,8 @@
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
p.headerless(resId == getBaseLayoutResource()
- || resId == getHeadsUpBaseLayoutResource());
+ || resId == getHeadsUpBaseLayoutResource()
+ || resId == R.layout.notification_template_material_media);
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
@@ -5092,7 +5089,7 @@
}
private CharSequence processTextSpans(CharSequence text) {
- if (hasForegroundColor() || mInNightMode) {
+ if (mInNightMode) {
return ContrastColorUtil.clearColorSpans(text);
}
return text;
@@ -5103,10 +5100,6 @@
contentView.setTextColor(id, getPrimaryTextColor(p));
}
- private boolean hasForegroundColor() {
- return mForegroundColor != COLOR_INVALID;
- }
-
/**
* @param p the template params to inflate this with
* @return the primary text color
@@ -5140,75 +5133,15 @@
|| mSecondaryTextColor == COLOR_INVALID
|| mTextColorsAreForBackground != backgroundColor) {
mTextColorsAreForBackground = backgroundColor;
- if (!hasForegroundColor() || !isColorized(p)) {
- mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
- backgroundColor, mInNightMode);
- mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
- backgroundColor, mInNightMode);
- if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
- mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- mPrimaryTextColor, backgroundColor, 4.5);
- mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- mSecondaryTextColor, backgroundColor, 4.5);
- }
- } else {
- double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
- double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
- double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
- backgroundColor);
- // We only respect the given colors if worst case Black or White still has
- // contrast
- boolean backgroundLight = backLum > textLum
- && satisfiesTextContrast(backgroundColor, Color.BLACK)
- || backLum <= textLum
- && !satisfiesTextContrast(backgroundColor, Color.WHITE);
- if (contrast < 4.5f) {
- if (backgroundLight) {
- mSecondaryTextColor = ContrastColorUtil.findContrastColor(
- mForegroundColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
- } else {
- mSecondaryTextColor =
- ContrastColorUtil.findContrastColorAgainstDark(
- mForegroundColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
- }
- } else {
- mPrimaryTextColor = mForegroundColor;
- mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
- mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
- : LIGHTNESS_TEXT_DIFFERENCE_DARK);
- if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
- backgroundColor) < 4.5f) {
- // oh well the secondary is not good enough
- if (backgroundLight) {
- mSecondaryTextColor = ContrastColorUtil.findContrastColor(
- mSecondaryTextColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- } else {
- mSecondaryTextColor
- = ContrastColorUtil.findContrastColorAgainstDark(
- mSecondaryTextColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- }
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, backgroundLight
- ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
- : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
- }
- }
+ mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+ backgroundColor, mInNightMode);
+ mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
+ backgroundColor, mInNightMode);
+ if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
+ mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ mPrimaryTextColor, backgroundColor, 4.5);
+ mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ mSecondaryTextColor, backgroundColor, 4.5);
}
}
}
@@ -5254,11 +5187,7 @@
result = new TemplateBindResult();
}
bindLargeIcon(contentView, p, result);
- if (p.mHeaderless) {
- // views in the headerless (collapsed) state
- result.mHeadingExtraMarginSet.applyToView(contentView,
- R.id.notification_headerless_view_column);
- } else {
+ if (!p.mHeaderless) {
// views in states with a header (big states)
result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
result.mTitleMarginSet.applyToView(contentView, R.id.title);
@@ -5278,6 +5207,8 @@
@NonNull TemplateBindResult result) {
final Resources resources = mContext.getResources();
final float density = resources.getDisplayMetrics().density;
+ final float iconMarginDp = resources.getDimension(
+ R.dimen.notification_right_icon_content_margin) / density;
final float contentMarginDp = resources.getDimension(
R.dimen.notification_content_margin_end) / density;
final float expanderSizeDp = resources.getDimension(
@@ -5299,7 +5230,7 @@
}
}
}
- final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp;
+ final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
result.setRightIconState(largeIconShown, viewWidthDp,
extraMarginEndDpIfVisible, expanderSizeDp);
}
@@ -5363,7 +5294,7 @@
private void bindHeaderChronometerAndTime(RemoteViews contentView,
StandardTemplateParams p, boolean hasTextToLeft) {
- if (showsTimeOrChronometer()) {
+ if (!p.mHideTime && showsTimeOrChronometer()) {
if (hasTextToLeft) {
contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
setTextViewColorSecondary(contentView, R.id.time_divider, p);
@@ -5394,6 +5325,9 @@
*/
private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
+ if (p.mHideSubText) {
+ return false;
+ }
CharSequence summaryText = p.summaryText;
if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
&& mStyle.hasSummaryInHeader()) {
@@ -5424,6 +5358,9 @@
*/
private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
+ if (p.mHideSubText) {
+ return false;
+ }
if (!TextUtils.isEmpty(p.headerTextSecondary)) {
contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
processLegacyText(p.headerTextSecondary)));
@@ -6654,7 +6591,7 @@
*/
private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) {
if (isColorized(p)) {
- return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
+ return getRawColor(p);
} else {
return COLOR_DEFAULT;
}
@@ -6682,21 +6619,6 @@
}
/**
- * Set a color palette to be used as the background and textColors
- *
- * @param backgroundColor the color to be used as the background
- * @param foregroundColor the color to be used as the foreground
- *
- * @hide
- */
- public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) {
- mBackgroundColor = backgroundColor;
- mForegroundColor = foregroundColor;
- mTextColorsAreForBackground = COLOR_INVALID;
- ensureColors(mParams.reset().fillTextsFrom(this));
- }
-
- /**
* Forces all styled remoteViews to be built from scratch and not use any cached
* RemoteViews.
* This is needed for legacy apps that are baking in their remoteviews into the
@@ -6752,24 +6674,14 @@
if (mLargeIcon != null || largeIcon != null) {
Resources resources = context.getResources();
Class<? extends Style> style = getNotificationStyle();
- int maxWidth = resources.getDimensionPixelSize(isLowRam
+ int maxSize = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_right_icon_size_low_ram
: R.dimen.notification_right_icon_size);
- int maxHeight = maxWidth;
- if (MediaStyle.class.equals(style)
- || DecoratedMediaCustomViewStyle.class.equals(style)) {
- maxHeight = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_media_image_max_height_low_ram
- : R.dimen.notification_media_image_max_height);
- maxWidth = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_media_image_max_width_low_ram
- : R.dimen.notification_media_image_max_width);
- }
if (mLargeIcon != null) {
- mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight);
+ mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
}
if (largeIcon != null) {
- largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight);
+ largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
}
}
reduceImageSizesForRemoteView(contentView, context, isLowRam);
@@ -6856,9 +6768,6 @@
* @hide
*/
public boolean isColorized() {
- if (isColorizedMedia()) {
- return true;
- }
return extras.getBoolean(EXTRA_COLORIZED)
&& (hasColorizedPermission() || isForegroundService());
}
@@ -6872,27 +6781,6 @@
}
/**
- * @return true if this notification is colorized and it is a media notification
- *
- * @hide
- */
- public boolean isColorizedMedia() {
- Class<? extends Style> style = getNotificationStyle();
- if (MediaStyle.class.equals(style)) {
- Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED);
- if ((colorized == null || colorized) && hasMediaSession()) {
- return true;
- }
- } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
- if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) {
- return true;
- }
- }
- return false;
- }
-
-
- /**
* @return true if this is a media notification
*
* @hide
@@ -7180,15 +7068,6 @@
/**
* @hide
- * @return true if the style positions the progress bar on the second line; false if the
- * style hides the progress bar
- */
- protected boolean hasProgress() {
- return true;
- }
-
- /**
- * @hide
* @return Whether we should put the summary be put into the notification header
*/
public boolean hasSummaryInHeader() {
@@ -7292,7 +7171,7 @@
* Set the content description of the big picture.
*/
@NonNull
- public BigPictureStyle bigPictureContentDescription(
+ public BigPictureStyle setContentDescription(
@Nullable CharSequence contentDescription) {
mPictureContentDescription = contentDescription;
return this;
@@ -9041,7 +8920,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- return makeMediaContentView();
+ return makeMediaContentView(null /* customContent */);
}
/**
@@ -9049,7 +8928,7 @@
*/
@Override
public RemoteViews makeBigContentView() {
- return makeMediaBigContentView();
+ return makeMediaBigContentView(null /* customContent */);
}
/**
@@ -9057,7 +8936,7 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- return makeMediaContentView();
+ return makeMediaContentView(null /* customContent */);
}
/** @hide */
@@ -9127,88 +9006,72 @@
container.setContentDescription(buttonId, action.title);
}
- private RemoteViews makeMediaContentView() {
- StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
- .hideProgress(true)
- .fillTextsFrom(mBuilder);
- RemoteViews view = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_media, p,
- null /* result */);
-
+ /** @hide */
+ protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
final int numActions = mBuilder.mActions.size();
- final int numActionsToShow = mActionsToShowInCompact == null
- ? 0
- : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
+ final int numActionsToShow = Math.min(mActionsToShowInCompact == null
+ ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
if (numActionsToShow > numActions) {
throw new IllegalArgumentException(String.format(
"setShowActionsInCompactView: action %d out of bounds (max %d)",
numActions, numActions - 1));
}
+
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .hideTime(numActionsToShow > 1) // hide if actions wider than a large icon
+ .hideSubText(numActionsToShow > 1) // hide if actions wider than a large icon
+ .hideLargeIcon(numActionsToShow > 0) // large icon or actions; not both
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+ TemplateBindResult result = new TemplateBindResult();
+ RemoteViews template = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_media, p,
+ null /* result */);
+
for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
if (i < numActionsToShow) {
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
- bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
+ bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
} else {
- view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+ template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
- handleImage(view);
- // handle the content margin
- int endMargin = R.dimen.notification_content_margin_end;
- if (mBuilder.mN.hasLargeIcon()) {
- endMargin = R.dimen.notification_media_image_margin_end;
- }
- view.setViewLayoutMarginDimen(R.id.notification_main_column,
- RemoteViews.MARGIN_END, endMargin);
- return view;
+ // Prevent a swooping expand animation when there are no actions
+ boolean hasActions = numActionsToShow != 0;
+ 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);
+ return template;
}
- private RemoteViews makeMediaBigContentView() {
+ /** @hide */
+ protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
- // Dont add an expanded view if there is no more content to be revealed
- int actionsInCompact = mActionsToShowInCompact == null
- ? 0
- : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
- if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
- return null;
- }
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG)
.hideProgress(true)
.fillTextsFrom(mBuilder);
- RemoteViews big = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_big_media, p , null /* result */);
+ TemplateBindResult result = new TemplateBindResult();
+ RemoteViews template = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_big_media, p , result);
for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
if (i < actionCount) {
- bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
+ bindMediaActionButton(template,
+ MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
} else {
- big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+ template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
- handleImage(big);
- return big;
- }
-
- private void handleImage(RemoteViews contentView) {
- if (mBuilder.mN.hasLargeIcon()) {
- contentView.setViewLayoutMarginDimen(R.id.title, RemoteViews.MARGIN_END, 0);
- contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0);
- }
- }
-
- /**
- * @hide
- */
- @Override
- protected boolean hasProgress() {
- return false;
+ buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
+ DevFlags.DECORATION_PARTIAL);
+ return template;
}
}
-
-
/**
* Helper class for generating large-format notifications that include a large image attachment.
*
@@ -9896,9 +9759,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
- return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
- mBuilder.mN.contentView);
+ return makeMediaContentView(mBuilder.mN.contentView);
}
/**
@@ -9906,24 +9767,10 @@
*/
@Override
public RemoteViews makeBigContentView() {
- RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
+ RemoteViews customContent = mBuilder.mN.bigContentView != null
? mBuilder.mN.bigContentView
: mBuilder.mN.contentView;
- return makeBigContentViewWithCustomContent(customRemoteView);
- }
-
- private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
- RemoteViews remoteViews = super.makeBigContentView();
- if (remoteViews != null) {
- return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
- customRemoteView);
- } else if (customRemoteView != mBuilder.mN.contentView){
- remoteViews = super.makeContentView(false /* increasedHeight */);
- return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
- customRemoteView);
- } else {
- return null;
- }
+ return makeMediaBigContentView(customContent);
}
/**
@@ -9931,10 +9778,10 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
+ RemoteViews customContent = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
- return makeBigContentViewWithCustomContent(customRemoteView);
+ return makeMediaBigContentView(customContent);
}
/**
@@ -9949,18 +9796,21 @@
return false;
}
- private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
- RemoteViews customContent) {
+ private RemoteViews buildIntoRemoteView(RemoteViews template, RemoteViews customContent,
+ boolean headerless) {
if (customContent != null) {
// Need to clone customContent before adding, because otherwise it can no longer be
// parceled independently of remoteViews.
customContent = customContent.clone();
customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
- remoteViews.removeAllViews(id);
- remoteViews.addView(id, customContent);
- remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
+ if (headerless) {
+ template.removeFromParent(R.id.notification_top_line);
+ }
+ template.removeAllViews(R.id.notification_main_column);
+ template.addView(R.id.notification_main_column, customContent);
+ template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
}
- return remoteViews;
+ return template;
}
}
@@ -12253,6 +12103,8 @@
boolean mHeaderless;
boolean mHideAppName;
boolean mHideTitle;
+ boolean mHideSubText;
+ boolean mHideTime;
boolean mHideActions;
boolean mHideProgress;
boolean mHideSnoozeButton;
@@ -12275,6 +12127,8 @@
mHeaderless = false;
mHideAppName = false;
mHideTitle = false;
+ mHideSubText = false;
+ mHideTime = false;
mHideActions = false;
mHideProgress = false;
mHideSnoozeButton = false;
@@ -12288,6 +12142,7 @@
summaryText = null;
headerTextSecondary = null;
maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
+ hideLargeIcon = false;
allowColorization = true;
mReduceHighlights = false;
return this;
@@ -12312,6 +12167,16 @@
return this;
}
+ public StandardTemplateParams hideSubText(boolean hideSubText) {
+ mHideSubText = hideSubText;
+ return this;
+ }
+
+ public StandardTemplateParams hideTime(boolean hideTime) {
+ mHideTime = hideTime;
+ return this;
+ }
+
final StandardTemplateParams hideActions(boolean hideActions) {
this.mHideActions = hideActions;
return this;
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 6b2e649..d640a6f 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -189,7 +189,7 @@
} else {
palette = Palette
.from(bitmap, new CelebiQuantizer())
- .maximumColorCount(256)
+ .maximumColorCount(5)
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
}
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/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index 6759346..51430c0 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,12 +33,14 @@
* Name of this restore set. May be user generated, may simply be the name
* of the handset model, e.g. "T-Mobile G1".
*/
+ @Nullable
public String name;
/**
* Identifier of the device whose data this is. This will be as unique as
* is practically possible; for example, it might be an IMEI.
*/
+ @Nullable
public String device;
/**
@@ -47,15 +50,48 @@
*/
public long token;
+ /**
+ * Properties of the {@link BackupTransport} transport that was used to obtain the data in
+ * this restore set.
+ */
+ public final int backupTransportFlags;
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ */
public RestoreSet() {
// Leave everything zero / null
+ backupTransportFlags = 0;
}
- public RestoreSet(String _name, String _dev, long _token) {
- name = _name;
- device = _dev;
- token = _token;
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ *
+ * @param name The name of the restore set.
+ * @param device The name of the device where the restore data is coming from.
+ * @param token The unique identifier for the current restore set.
+ */
+ public RestoreSet(@Nullable String name, @Nullable String device, long token) {
+ this(name, device, token, /* backupTransportFlags */ 0);
+ }
+
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ *
+ * @param name The name of the restore set.
+ * @param device The name of the device where the restore data is coming from.
+ * @param token The unique identifier for the current restore set.
+ * @param backupTransportFlags Flags returned by {@link BackupTransport#getTransportFlags()}
+ * implementation of the backup transport used by the source device
+ * to create this restore set. See {@link BackupAgent} for possible
+ * flag values.
+ */
+ public RestoreSet(@Nullable String name, @Nullable String device, long token,
+ int backupTransportFlags) {
+ this.name = name;
+ this.device = device;
+ this.token = token;
+ this.backupTransportFlags = backupTransportFlags;
}
// Parcelable implementation
@@ -67,6 +103,7 @@
out.writeString(name);
out.writeString(device);
out.writeLong(token);
+ out.writeInt(backupTransportFlags);
}
public static final @android.annotation.NonNull Parcelable.Creator<RestoreSet> CREATOR
@@ -84,5 +121,6 @@
name = in.readString();
device = in.readString();
token = in.readLong();
+ backupTransportFlags = in.readInt();
}
}
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/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/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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fa6472e..4674aa2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -6030,7 +6030,7 @@
pw.print(":");
for (int it=0; it<types.size(); it++) {
pw.print(" ");
- pw.print(JobParameters.getReasonCodeDescription(types.keyAt(it)));
+ pw.print(JobParameters.getLegacyReasonCodeDescription(types.keyAt(it)));
pw.print("(");
pw.print(types.valueAt(it));
pw.print("x)");
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..95b5e85 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -28,6 +28,10 @@
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.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.MathUtils;
import com.android.internal.util.Preconditions;
@@ -45,11 +49,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 +180,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 +240,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, (int) timings[i]));
+ }
+ VibrationEffect effect = new Composed(segments, repeat);
effect.validate();
return effect;
}
@@ -317,7 +326,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;
}
@@ -428,32 +438,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 +522,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 +676,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 +714,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 +765,10 @@
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 primitive to the end of the current composition.
@@ -1181,9 +781,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 +796,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 +811,21 @@
* @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;
}
@@ -1230,22 +840,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 +855,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 +879,14 @@
return Integer.toString(id);
}
}
-
-
- /**
- * @hide
- */
- public static class PrimitiveEffect implements Parcelable {
- public int id;
- public float scale;
- public int delay;
-
- PrimitiveEffect(int id, float scale, int delay) {
- this.id = id;
- this.scale = scale;
- this.delay = delay;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(id);
- dest.writeFloat(scale);
- dest.writeInt(delay);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @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];
- }
- };
- }
}
- 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..9c46bc9 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -153,7 +153,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);
}
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/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
new file mode 100644
index 0000000..61a5d6c
--- /dev/null
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -0,0 +1,152 @@
+/*
+ * 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 for a
+ * specified duration.
+ *
+ * @hide
+ */
+@TestApi
+public final class StepSegment extends VibrationEffectSegment {
+ private final float mAmplitude;
+ private final int mDuration;
+
+ StepSegment(@NonNull Parcel in) {
+ this(in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public StepSegment(float amplitude, int duration) {
+ mAmplitude = amplitude;
+ 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
+ && mDuration == other.mDuration;
+ }
+
+ public float getAmplitude() {
+ return mAmplitude;
+ }
+
+ @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, 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), mDuration);
+ }
+
+ @NonNull
+ @Override
+ public StepSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAmplitude, mDuration);
+ }
+
+ @Override
+ public String toString() {
+ return "Step{amplitude=" + mAmplitude
+ + ", 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.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..3dc9e12
--- /dev/null
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -0,0 +1,114 @@
+/*
+ * 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 value to be held 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;
+
+ /** 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_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/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index e68f330..7e40497 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -358,6 +358,38 @@
public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
/**
+ * Namespace for all statsd java features that can be applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
+
+ /**
+ * Namespace for all statsd java features that are applied on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
+
+ /**
+ * Namespace for all statsd native features that can be applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
+
+ /**
+ * Namespace for all statsd native features that are applied on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
+
+ /**
* Namespace for storage-related features.
*
* @deprecated Replace storage namespace with storage_native_boot.
@@ -456,7 +488,8 @@
*/
@NonNull
private static final List<String> PUBLIC_NAMESPACES =
- Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME);
+ Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
+ NAMESPACE_STATSD_JAVA_BOOT);
/**
* Privacy related properties definitions.
*
@@ -505,38 +538,6 @@
"connectivity_thermal_power_manager";
/**
- * Namespace for all statsd java features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
-
- /**
- * Namespace for all statsd java features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
-
- /**
- * Namespace for all statsd native features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
-
- /**
- * Namespace for all statsd native features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
-
- /**
* Namespace for configuration related features.
*
* @hide
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
index 8d798c3..297f00a4 100644
--- a/core/java/android/service/translation/ITranslationService.aidl
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -16,7 +16,8 @@
package android.service.translation;
-import android.view.translation.TranslationSpec;
+import android.os.ResultReceiver;
+import android.view.translation.TranslationContext;
import com.android.internal.os.IResultReceiver;
/**
@@ -31,6 +32,9 @@
oneway interface ITranslationService {
void onConnected();
void onDisconnected();
- void onCreateTranslationSession(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
- int sessionId, in IResultReceiver receiver);
+ void onCreateTranslationSession(in TranslationContext translationContext, int sessionId,
+ in IResultReceiver receiver);
+
+ void onTranslationCapabilitiesRequest(int sourceFormat, int targetFormat,
+ in ResultReceiver receiver);
}
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
index c5f1f04..7edf2e2 100644
--- a/core/java/android/service/translation/TranslationService.java
+++ b/core/java/android/service/translation/TranslationService.java
@@ -34,8 +34,12 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.ArraySet;
import android.util.Log;
import android.view.translation.ITranslationDirectManager;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationManager;
import android.view.translation.TranslationRequest;
import android.view.translation.TranslationResponse;
@@ -43,6 +47,9 @@
import com.android.internal.os.IResultReceiver;
+import java.util.Set;
+import java.util.function.Consumer;
+
/**
* Service for translating text.
* @hide
@@ -92,10 +99,20 @@
}
@Override
- public void onCreateTranslationSession(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ public void onCreateTranslationSession(TranslationContext translationContext,
int sessionId, IResultReceiver receiver) throws RemoteException {
mHandler.sendMessage(obtainMessage(TranslationService::handleOnCreateTranslationSession,
- TranslationService.this, sourceSpec, destSpec, sessionId, receiver));
+ TranslationService.this, translationContext, sessionId, receiver));
+ }
+
+ @Override
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ mHandler.sendMessage(
+ obtainMessage(TranslationService::handleOnTranslationCapabilitiesRequest,
+ TranslationService.this, sourceFormat, targetFormat,
+ resultReceiver));
}
};
@@ -194,14 +211,13 @@
/**
* TODO: fill in javadoc.
*
- * @param sourceSpec
- * @param destSpec
+ * @param translationContext
* @param sessionId
*/
// TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
// solution to make it's safe.
- public abstract void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId);
+ public abstract void onCreateTranslationSession(@NonNull TranslationContext translationContext,
+ int sessionId);
/**
* TODO: fill in javadoc.
@@ -222,12 +238,27 @@
@NonNull CancellationSignal cancellationSignal,
@NonNull OnTranslationResultCallback callback);
+ /**
+ * TODO: fill in javadoc
+ * TODO: make this abstract again once aiai is ready.
+ *
+ * <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p>
+ *
+ * @param sourceFormat
+ * @param targetFormat
+ * @param callback
+ */
+ public abstract void onTranslationCapabilitiesRequest(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull Consumer<Set<TranslationCapability>> callback);
+
// TODO(b/176464808): Need to handle client dying case
- // TODO(b/176464808): Need to handle the failure case. e.g. if the specs does not support
+ // TODO(b/176464808): Need to handle the failure case. e.g. if the context is not supported.
- private void handleOnCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ private void handleOnCreateTranslationSession(@NonNull TranslationContext translationContext,
+ int sessionId, IResultReceiver resultReceiver) {
try {
final Bundle extras = new Bundle();
extras.putBinder(EXTRA_SERVICE_BINDER, mClientInterface.asBinder());
@@ -236,6 +267,24 @@
} catch (RemoteException e) {
Log.w(TAG, "RemoteException sending client interface: " + e);
}
- onCreateTranslationSession(sourceSpec, destSpec, sessionId);
+ onCreateTranslationSession(translationContext, sessionId);
+ }
+
+ private void handleOnTranslationCapabilitiesRequest(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ onTranslationCapabilitiesRequest(sourceFormat, targetFormat,
+ new Consumer<Set<TranslationCapability>>() {
+ @Override
+ public void accept(Set<TranslationCapability> values) {
+ final ArraySet<TranslationCapability> capabilities = new ArraySet<>(values);
+ final Bundle bundle = new Bundle();
+ bundle.putParcelableArray(TranslationManager.EXTRA_CAPABILITIES,
+ capabilities.toArray(new TranslationCapability[0]));
+ resultReceiver.send(TranslationManager.STATUS_SYNC_CALL_SUCCESS, bundle);
+ }
+ });
+
}
}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index d3eaeae..2a43222 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -26,6 +26,10 @@
import java.util.Locale;
/**
+ * API for sending log output to the {@link Log#LOG_ID_SYSTEM} buffer.
+ *
+ * <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
+ *
* @hide
*/
public final class Slog {
@@ -51,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;
@@ -71,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;
@@ -90,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;
@@ -114,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;
@@ -123,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;
@@ -143,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;
@@ -152,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/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
new file mode 100644
index 0000000..dc93a47
--- /dev/null
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -0,0 +1,166 @@
+/*
+ * 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.util;
+
+/**
+ * SparseDoubleArrays map integers to doubles. Unlike a normal array of doubles,
+ * there can be gaps in the indices. It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Doubles, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys. The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items. It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ *
+ * @see SparseLongArray
+ *
+ * @hide
+ */
+public class SparseDoubleArray implements Cloneable {
+ /**
+ * The int->double map, but storing the doubles as longs using
+ * {@link Double#doubleToRawLongBits(double)}.
+ */
+ private SparseLongArray mValues;
+
+ /** Creates a new SparseDoubleArray containing no mappings. */
+ public SparseDoubleArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseDoubleArray, containing no mappings, that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
+ */
+ public SparseDoubleArray(int initialCapacity) {
+ mValues = new SparseLongArray(initialCapacity);
+ }
+
+ @Override
+ public SparseDoubleArray clone() {
+ SparseDoubleArray clone = null;
+ try {
+ clone = (SparseDoubleArray) super.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse) {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Gets the double mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public double get(int key) {
+ final int index = mValues.indexOfKey(key);
+ if (index < 0) {
+ return 0.0d;
+ }
+ return valueAt(index);
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(int key, double value) {
+ mValues.put(key, Double.doubleToRawLongBits(value));
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * <b>adding</b> its value to the previous mapping from the specified key if there
+ * was one.
+ *
+ * <p>This differs from {@link #put} because instead of replacing any previous value, it adds
+ * (in the numerical sense) to it.
+ */
+ public void add(int key, double summand) {
+ final double oldValue = get(key);
+ put(key, oldValue + summand);
+ }
+
+ /** Returns the number of key-value mappings that this SparseDoubleArray currently stores. */
+ public int size() {
+ return mValues.size();
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseDoubleArray stores.
+ *
+ * @see SparseLongArray#keyAt(int)
+ */
+ public int keyAt(int index) {
+ return mValues.keyAt(index);
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseDoubleArray stores.
+ *
+ * @see SparseLongArray#valueAt(int)
+ */
+ public double valueAt(int index) {
+ return Double.longBitsToDouble(mValues.valueAt(index));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation composes a string by iterating over its mappings.
+ */
+ @Override
+ public String toString() {
+ if (size() <= 0) {
+ return "{}";
+ }
+
+ StringBuilder buffer = new StringBuilder(size() * 34);
+ buffer.append('{');
+ for (int i = 0; i < size(); i++) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ int key = keyAt(i);
+ buffer.append(key);
+ buffer.append('=');
+ double value = valueAt(i);
+ buffer.append(value);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
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/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
index c159a12..7fac6c5 100644
--- a/core/java/android/view/InputEventAssigner.java
+++ b/core/java/android/view/InputEventAssigner.java
@@ -45,13 +45,13 @@
public class InputEventAssigner {
private static final String TAG = "InputEventAssigner";
private boolean mHasUnprocessedDown = false;
- private int mEventId = INVALID_INPUT_EVENT_ID;
+ private int mDownEventId = INVALID_INPUT_EVENT_ID;
/**
- * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset
- * the 'down' state to assign the latest input event to the current frame.
+ * Notify InputEventAssigner that a frame has been processed. We no longer need to keep track of
+ * the DOWN event because a frame has already been produced for it.
*/
- public void onChoreographerCallback() {
+ public void notifyFrameProcessed() {
// Mark completion of this frame. Use newest input event from now on.
mHasUnprocessedDown = false;
}
@@ -62,31 +62,22 @@
* @return the id of the input event to use for the current frame
*/
public int processEvent(InputEvent event) {
- if (event instanceof KeyEvent) {
- // We will not do any special handling for key events
- return event.getId();
- }
-
if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
- final int action = motionEvent.getActionMasked();
-
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- mHasUnprocessedDown = false;
+ if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) {
+ final int action = motionEvent.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mHasUnprocessedDown = true;
+ mDownEventId = event.getId();
+ }
+ if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) {
+ return mDownEventId;
+ }
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mHasUnprocessedDown = false;
+ }
}
- if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) {
- mHasUnprocessedDown = true;
- mEventId = event.getId();
- // This will remain 'true' even if we receive a MOVE event, as long as choreographer
- // hasn't invoked the 'CALLBACK_INPUT' callback.
- }
- // Don't update the event id if we haven't processed DOWN yet.
- if (!mHasUnprocessedDown) {
- mEventId = event.getId();
- }
- return mEventId;
}
-
- throw new IllegalArgumentException("Received unexpected " + event);
+ return event.getId();
}
}
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index 05636de..bd20f5b 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -26,6 +26,9 @@
import com.android.internal.R;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* The top line of content in a notification view.
* This includes the text views and badges but excludes the icon and the expander.
@@ -34,17 +37,23 @@
*/
@RemoteViews.RemoteView
public class NotificationTopLineView extends ViewGroup {
+ private final OverflowAdjuster mOverflowAdjuster = new OverflowAdjuster();
private final int mGravityY;
private final int mChildMinWidth;
+ private final int mChildHideWidth;
@Nullable private View mAppName;
@Nullable private View mTitle;
private View mHeaderText;
+ private View mHeaderTextDivider;
private View mSecondaryHeaderText;
+ private View mSecondaryHeaderTextDivider;
private OnClickListener mFeedbackListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
private View mFeedbackIcon;
private int mHeaderTextMarginEnd;
+ private Set<View> mViewsToDisappear = new HashSet<>();
+
private int mMaxAscent;
private int mMaxDescent;
@@ -66,6 +75,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = getResources();
mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+ mChildHideWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_hide_width);
// NOTE: Implementation only supports TOP, BOTTOM, and CENTER_VERTICAL gravities,
// with CENTER_VERTICAL being the default.
@@ -88,7 +98,9 @@
mAppName = findViewById(R.id.app_name_text);
mTitle = findViewById(R.id.title);
mHeaderText = findViewById(R.id.header_text);
+ mHeaderTextDivider = findViewById(R.id.header_text_divider);
mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
+ mSecondaryHeaderTextDivider = findViewById(R.id.header_text_secondary_divider);
mFeedbackIcon = findViewById(R.id.feedback);
}
@@ -125,48 +137,37 @@
maxChildHeight = Math.max(maxChildHeight, childHeight);
}
+ mViewsToDisappear.clear();
// Ensure that there is at least enough space for the icons
int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd());
if (totalWidth > givenWidth - endMargin) {
int overFlow = totalWidth - givenWidth + endMargin;
- // First shrink the app name, down to a minimum size
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mAppName, mChildMinWidth);
-
- // Next, shrink the header text (this usually has subText)
- // This shrinks the subtext first, but not all the way (yet!)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, mChildMinWidth);
-
- // Next, shrink the secondary header text (this rarely has conversationTitle)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mSecondaryHeaderText, 0);
-
- // Next, shrink the title text (this has contentTitle; only in headerless views)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mTitle, mChildMinWidth);
-
- // Finally, if there is still overflow, shrink the header down to 0 if still necessary.
- shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, 0);
+ mOverflowAdjuster.resetForOverflow(overFlow, heightSpec)
+ // First shrink the app name, down to a minimum size
+ .adjust(mAppName, null, mChildMinWidth)
+ // Next, shrink the header text (this usually has subText)
+ // This shrinks the subtext first, but not all the way (yet!)
+ .adjust(mHeaderText, mHeaderTextDivider, mChildMinWidth)
+ // Next, shrink the secondary header text (this rarely has conversationTitle)
+ .adjust(mSecondaryHeaderText, mSecondaryHeaderTextDivider, 0)
+ // Next, shrink the title text (this has contentTitle; only in headerless views)
+ .adjust(mTitle, null, mChildMinWidth)
+ // Next, shrink the header down to 0 if still necessary.
+ .adjust(mHeaderText, mHeaderTextDivider, 0)
+ // Finally, shrink the title to 0 if necessary (media is super cramped)
+ .adjust(mTitle, null, 0)
+ // Clean up
+ .finish();
}
setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
}
- private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
- int minimumWidth) {
- if (targetView != null) {
- final int oldWidth = targetView.getMeasuredWidth();
- if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
- // we're still too big
- int newSize = Math.max(minimumWidth, oldWidth - overFlow);
- int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
- targetView.measure(childWidthSpec, heightSpec);
- overFlow -= oldWidth - newSize;
- }
- }
- return overFlow;
- }
-
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int left = getPaddingStart();
+ final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ final int width = getWidth();
+ int start = getPaddingStart();
int childCount = getChildCount();
int ownHeight = b - t;
int childSpace = ownHeight - mPaddingTop - mPaddingBottom;
@@ -182,8 +183,6 @@
}
int childHeight = child.getMeasuredHeight();
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
- int layoutLeft;
- int layoutRight;
// Calculate vertical alignment of the views, accounting for the view baselines
int childTop;
@@ -219,19 +218,16 @@
default:
childTop = mPaddingTop;
}
-
- left += params.getMarginStart();
- int right = left + child.getMeasuredWidth();
- layoutLeft = left;
- layoutRight = right;
- left = right + params.getMarginEnd();
-
- if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- int ltrLeft = layoutLeft;
- layoutLeft = getWidth() - layoutRight;
- layoutRight = getWidth() - ltrLeft;
+ if (mViewsToDisappear.contains(child)) {
+ child.layout(start, childTop, start, childTop + childHeight);
+ } else {
+ start += params.getMarginStart();
+ int end = start + child.getMeasuredWidth();
+ int layoutLeft = isRtl ? width - end : start;
+ int layoutRight = isRtl ? width - start : end;
+ start = end + params.getMarginEnd();
+ child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
}
- child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
}
updateTouchListener();
}
@@ -400,4 +396,83 @@
}
return mTouchListener.onTouchUp(upX, upY, downX, downY);
}
+
+ private final class OverflowAdjuster {
+ private int mOverflow;
+ private int mHeightSpec;
+ private View mRegrowView;
+
+ OverflowAdjuster resetForOverflow(int overflow, int heightSpec) {
+ mOverflow = overflow;
+ mHeightSpec = heightSpec;
+ mRegrowView = null;
+ return this;
+ }
+
+ /**
+ * Shrink the targetView's width by up to overFlow, down to minimumWidth.
+ * @param targetView the view to shrink the width of
+ * @param targetDivider a divider view which should be set to 0 width if the targetView is
+ * @param minimumWidth the minimum width allowed for the targetView
+ * @return this object
+ */
+ OverflowAdjuster adjust(View targetView, View targetDivider, int minimumWidth) {
+ if (mOverflow <= 0 || targetView == null || targetView.getVisibility() == View.GONE) {
+ return this;
+ }
+ final int oldWidth = targetView.getMeasuredWidth();
+ if (oldWidth <= minimumWidth) {
+ return this;
+ }
+ // we're too big
+ int newSize = Math.max(minimumWidth, oldWidth - mOverflow);
+ if (minimumWidth == 0 && newSize < mChildHideWidth
+ && mRegrowView != null && mRegrowView != targetView) {
+ // View is so small it's better to hide it entirely (and its divider and margins)
+ // so we can give that space back to another previously shrunken view.
+ newSize = 0;
+ }
+
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ targetView.measure(childWidthSpec, mHeightSpec);
+ mOverflow -= oldWidth - newSize;
+
+ if (newSize == 0) {
+ mViewsToDisappear.add(targetView);
+ mOverflow -= getHorizontalMargins(targetView);
+ if (targetDivider != null && targetDivider.getVisibility() != GONE) {
+ mViewsToDisappear.add(targetDivider);
+ int oldDividerWidth = targetDivider.getMeasuredWidth();
+ int dividerWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.AT_MOST);
+ targetDivider.measure(dividerWidthSpec, mHeightSpec);
+ mOverflow -= (oldDividerWidth + getHorizontalMargins(targetDivider));
+ }
+ }
+ if (mOverflow < 0 && mRegrowView != null) {
+ // We're now under-flowing, so regrow the last view.
+ final int regrowCurrentSize = mRegrowView.getMeasuredWidth();
+ final int maxSize = regrowCurrentSize - mOverflow;
+ int regrowWidthSpec = MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST);
+ mRegrowView.measure(regrowWidthSpec, mHeightSpec);
+ finish();
+ return this;
+ }
+
+ if (newSize != 0) {
+ // if we shrunk this view (but did not completely hide it) store it for potential
+ // re-growth if we proactively shorten a future view.
+ mRegrowView = targetView;
+ }
+ return this;
+ }
+
+ void finish() {
+ resetForOverflow(0, 0);
+ }
+
+ private int getHorizontalMargins(View view) {
+ MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
+ return params.getMarginStart() + params.getMarginEnd();
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 3ffe0c6..f1eef9f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1296,7 +1296,7 @@
mBlastBufferQueue.destroy();
}
mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight, mFormat, true /* TODO */);
+ mSurfaceHeight, mFormat);
}
private void onDrawFinished() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4fb611..dbccf10 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -330,7 +330,6 @@
private boolean mUseBLASTAdapter;
private boolean mForceDisableBLAST;
- private boolean mEnableTripleBuffering;
private boolean mFastScrollSoundEffectsEnabled;
@@ -474,6 +473,7 @@
FrameInfo frameInfo = mChoreographer.mFrameInfo;
mViewFrameInfo.populateFrameInfo(frameInfo);
mViewFrameInfo.reset();
+ mInputEventAssigner.notifyFrameProcessed();
return frameInfo;
}
@@ -1179,9 +1179,6 @@
if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
mUseBLASTAdapter = true;
}
- if ((res & WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING) != 0) {
- mEnableTripleBuffering = true;
- }
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
@@ -1373,17 +1370,10 @@
// can be used by code on the system process to escape that and enable
// HW accelerated drawing. (This is basically for the lock screen.)
- final boolean fakeHwAccelerated = (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
final boolean forceHwAccelerated = (attrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
- if (fakeHwAccelerated) {
- // This is exclusively for the preview windows the window manager
- // shows for launching applications, so they will look more like
- // the app being launched.
- mAttachInfo.mHardwareAccelerationRequested = true;
- } else if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
+ if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.destroy();
}
@@ -1907,7 +1897,7 @@
Surface ret = null;
if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, width, height,
- format, mEnableTripleBuffering);
+ format);
// We only return the Surface the first time, as otherwise
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
@@ -8502,11 +8492,6 @@
consumedBatches = false;
}
doProcessInputEvents();
- if (consumedBatches) {
- // Must be done after we processed the input events, to mark the completion of the frame
- // from the input point of view
- mInputEventAssigner.onChoreographerCallback();
- }
return consumedBatches;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 04512c9..9df87dc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2218,26 +2218,6 @@
public int flags;
/**
- * If the window has requested hardware acceleration, but this is not
- * allowed in the process it is in, then still render it as if it is
- * hardware accelerated. This is used for the starting preview windows
- * in the system process, which don't need to have the overhead of
- * hardware acceleration (they are just a static rendering), but should
- * be rendered as such to match the actual window of the app even if it
- * is hardware accelerated.
- * Even if the window isn't hardware accelerated, still do its rendering
- * as if it was.
- * Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows
- * that need hardware acceleration (e.g. LockScreen), where hardware acceleration
- * is generally disabled. This flag must be specified in addition to
- * {@link #FLAG_HARDWARE_ACCELERATED} to enable hardware acceleration for system
- * windows.
- *
- * @hide
- */
- public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
-
- /**
* In the system process, we globally do not use hardware acceleration
* because there are many threads doing UI there and they conflict.
* If certain parts of the UI that really do want to use hardware
@@ -2463,7 +2443,6 @@
* @hide
*/
@IntDef(flag = true, prefix="PRIVATE_FLAG_", value = {
- PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
@@ -2499,10 +2478,6 @@
@UnsupportedAppUsage
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
- equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
- name = "FAKE_HARDWARE_ACCELERATED"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
name = "FORCE_HARDWARE_ACCELERATED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 47ac1ee..18013e8 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -118,7 +118,6 @@
public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
public static final int ADD_FLAG_APP_VISIBLE = 0x2;
- public static final int ADD_FLAG_USE_TRIPLE_BUFFERING = 0x4;
public static final int ADD_FLAG_USE_BLAST = 0x8;
/**
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 37220fe..c5bce28 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -608,7 +608,12 @@
Preconditions.checkArgumentNonnegative(afterLength);
final Editable content = getEditable();
- if (content == null) return null;
+ // If {@link #getEditable()} is null or {@code mEditable} is equal to {@link #getEditable()}
+ // (a.k.a, a fake editable), it means we cannot get valid content from the editable, so
+ // fallback to retrieve surrounding text from other APIs.
+ if (content == null || mEditable == content) {
+ return InputConnection.super.getSurroundingText(beforeLength, afterLength, flags);
+ }
int selStart = Selection.getSelectionStart(content);
int selEnd = Selection.getSelectionEnd(content);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 34a60bb..38019c9 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -325,16 +325,16 @@
CharSequence textBeforeCursor = getTextBeforeCursor(beforeLength, flags);
if (textBeforeCursor == null) {
- textBeforeCursor = "";
+ return null;
+ }
+ CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
+ if (textAfterCursor == null) {
+ return null;
}
CharSequence selectedText = getSelectedText(flags);
if (selectedText == null) {
selectedText = "";
}
- CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
- if (textAfterCursor == null) {
- textAfterCursor = "";
- }
CharSequence surroundingText =
TextUtils.concat(textBeforeCursor, selectedText, textAfterCursor);
return new SurroundingText(surroundingText, textBeforeCursor.length(),
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index d347f31..dbc32e9 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -18,7 +18,9 @@
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.ResultReceiver;
import android.view.autofill.AutofillId;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import com.android.internal.os.IResultReceiver;
@@ -30,17 +32,17 @@
* {@hide}
*/
oneway interface ITranslationManager {
- void getSupportedLocales(in IResultReceiver receiver, int userId);
- void onSessionCreated(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+ void onTranslationCapabilitiesRequest(int sourceFormat, int destFormat,
+ in ResultReceiver receiver, int userId);
+ void onSessionCreated(in TranslationContext translationContext,
int sessionId, in IResultReceiver receiver, int userId);
void updateUiTranslationState(int state, in TranslationSpec sourceSpec,
- in TranslationSpec destSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
+ in TranslationSpec targetSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
int userId);
// deprecated
void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec,
- in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId,
- int userId);
+ in TranslationSpec targetSpec, in List<AutofillId> viewIds, int taskId, int userId);
void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationCapability.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationCapability.aidl
index cab5ad2..3a80b17 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationCapability.aidl
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package android.view.translation;
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+parcelable TranslationCapability;
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
new file mode 100644
index 0000000..28b2113
--- /dev/null
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -0,0 +1,289 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Capability class holding information for a pair of {@link TranslationSpec}s.
+ *
+ * <p>Holds information and limitations on how to create a {@link TranslationContext} which can
+ * be used by {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genConstructor = false)
+public final class TranslationCapability implements Parcelable {
+
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_DOWNLOADING = 2;
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_ON_DEVICE = 3;
+
+ /**
+ * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+ */
+ private final @ModelState int mState;
+
+ /**
+ * {@link TranslationSpec} describing the source data specs for this
+ * capability.
+ */
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+
+ /**
+ * {@link TranslationSpec} describing the target data specs for this
+ * capability.
+ */
+ @NonNull
+ private final TranslationSpec mTargetSpec;
+
+ /**
+ * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+ *
+ * <p>Translation service will still support translation requests for this capability.</p>
+ */
+ private final boolean mUiTranslationEnabled;
+
+ /**
+ * Translation flags for settings that are supported by the
+ * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+ * provided in this capability.
+ */
+ private final @TranslationContext.TranslationFlag int mSupportedTranslationFlags;
+
+ /**
+ * Constructor for creating a {@link TranslationCapability}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public TranslationCapability(@ModelState int state, @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec, boolean uiTranslationEnabled,
+ @TranslationContext.TranslationFlag int supportedTranslationFlags) {
+ Objects.requireNonNull(sourceSpec, "sourceSpec should not be null");
+ Objects.requireNonNull(targetSpec, "targetSpec should not be null");
+
+ this.mState = state;
+ this.mSourceSpec = sourceSpec;
+ this.mTargetSpec = targetSpec;
+ this.mUiTranslationEnabled = uiTranslationEnabled;
+ this.mSupportedTranslationFlags = supportedTranslationFlags;
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationCapability.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "STATE_", value = {
+ STATE_AVAILABLE_TO_DOWNLOAD,
+ STATE_DOWNLOADING,
+ STATE_ON_DEVICE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface ModelState {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String modelStateToString(@ModelState int value) {
+ switch (value) {
+ case STATE_AVAILABLE_TO_DOWNLOAD:
+ return "STATE_AVAILABLE_TO_DOWNLOAD";
+ case STATE_DOWNLOADING:
+ return "STATE_DOWNLOADING";
+ case STATE_ON_DEVICE:
+ return "STATE_ON_DEVICE";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+ */
+ @DataClass.Generated.Member
+ public @ModelState int getState() {
+ return mState;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the source data specs for this
+ * capability.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getSourceSpec() {
+ return mSourceSpec;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the target data specs for this
+ * capability.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getTargetSpec() {
+ return mTargetSpec;
+ }
+
+ /**
+ * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+ *
+ * <p>Translation service will still support translation requests for this capability.</p>
+ */
+ @DataClass.Generated.Member
+ public boolean isUiTranslationEnabled() {
+ return mUiTranslationEnabled;
+ }
+
+ /**
+ * Translation flags for settings that are supported by the
+ * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+ * provided in this capability.
+ */
+ @DataClass.Generated.Member
+ public @TranslationContext.TranslationFlag int getSupportedTranslationFlags() {
+ return mSupportedTranslationFlags;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationCapability { " +
+ "state = " + modelStateToString(mState) + ", " +
+ "sourceSpec = " + mSourceSpec + ", " +
+ "targetSpec = " + mTargetSpec + ", " +
+ "uiTranslationEnabled = " + mUiTranslationEnabled + ", " +
+ "supportedTranslationFlags = " + mSupportedTranslationFlags +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mUiTranslationEnabled) flg |= 0x8;
+ dest.writeByte(flg);
+ dest.writeInt(mState);
+ dest.writeTypedObject(mSourceSpec, flags);
+ dest.writeTypedObject(mTargetSpec, flags);
+ dest.writeInt(mSupportedTranslationFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationCapability(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean uiTranslationEnabled = (flg & 0x8) != 0;
+ int state = in.readInt();
+ TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ int supportedTranslationFlags = in.readInt();
+
+ this.mState = state;
+
+ if (!(mState == STATE_AVAILABLE_TO_DOWNLOAD)
+ && !(mState == STATE_DOWNLOADING)
+ && !(mState == STATE_ON_DEVICE)) {
+ throw new java.lang.IllegalArgumentException(
+ "state was " + mState + " but must be one of: "
+ + "STATE_AVAILABLE_TO_DOWNLOAD(" + STATE_AVAILABLE_TO_DOWNLOAD + "), "
+ + "STATE_DOWNLOADING(" + STATE_DOWNLOADING + "), "
+ + "STATE_ON_DEVICE(" + STATE_ON_DEVICE + ")");
+ }
+
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mUiTranslationEnabled = uiTranslationEnabled;
+ this.mSupportedTranslationFlags = supportedTranslationFlags;
+ com.android.internal.util.AnnotationValidations.validate(
+ TranslationContext.TranslationFlag.class, null, mSupportedTranslationFlags);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationCapability> CREATOR
+ = new Parcelable.Creator<TranslationCapability>() {
+ @Override
+ public TranslationCapability[] newArray(int size) {
+ return new TranslationCapability[size];
+ }
+
+ @Override
+ public TranslationCapability createFromParcel(@NonNull android.os.Parcel in) {
+ return new TranslationCapability(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1616438309593L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
+ inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationContext.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationContext.aidl
index cab5ad2..cb6c23f 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationContext.aidl
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package android.view.translation;
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+parcelable TranslationContext;
diff --git a/core/java/android/view/translation/TranslationContext.java b/core/java/android/view/translation/TranslationContext.java
new file mode 100644
index 0000000..1d3d182
--- /dev/null
+++ b/core/java/android/view/translation/TranslationContext.java
@@ -0,0 +1,309 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Info class holding information for {@link Translator}s and used by
+ * {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genBuilder = true)
+public final class TranslationContext implements Parcelable {
+
+ /**
+ * This context will perform translations in low latency mode.
+ */
+ public static final @TranslationFlag int FLAG_LOW_LATENCY = 0x1;
+ /**
+ * This context will enable the {@link Translator} to return transliteration results.
+ */
+ public static final @TranslationFlag int FLAG_TRANSLITERATION = 0x2;
+ /**
+ * This context will enable the {@link Translator} to return dictionary results.
+ */
+ public static final @TranslationFlag int FLAG_DICTIONARY_DESCRIPTION = 0x4;
+
+ /**
+ * {@link TranslationSpec} describing the source data to be translated.
+ */
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+
+ /**
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ @NonNull
+ private final TranslationSpec mTargetSpec;
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ private final @TranslationFlag int mTranslationFlags;
+
+ private static int defaultTranslationFlags() {
+ return 0;
+ }
+
+ @DataClass.Suppress({"setSourceSpec", "setTargetSpec"})
+ abstract static class BaseBuilder {
+
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationContext.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @android.annotation.IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_LOW_LATENCY,
+ FLAG_TRANSLITERATION,
+ FLAG_DICTIONARY_DESCRIPTION
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TranslationFlag {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String translationFlagToString(@TranslationFlag int value) {
+ return com.android.internal.util.BitUtils.flagsToString(
+ value, TranslationContext::singleTranslationFlagToString);
+ }
+
+ @DataClass.Generated.Member
+ static String singleTranslationFlagToString(@TranslationFlag int value) {
+ switch (value) {
+ case FLAG_LOW_LATENCY:
+ return "FLAG_LOW_LATENCY";
+ case FLAG_TRANSLITERATION:
+ return "FLAG_TRANSLITERATION";
+ case FLAG_DICTIONARY_DESCRIPTION:
+ return "FLAG_DICTIONARY_DESCRIPTION";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ TranslationContext(
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec,
+ @TranslationFlag int translationFlags) {
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mTranslationFlags = translationFlags;
+
+ com.android.internal.util.Preconditions.checkFlagsArgument(
+ mTranslationFlags,
+ FLAG_LOW_LATENCY
+ | FLAG_TRANSLITERATION
+ | FLAG_DICTIONARY_DESCRIPTION);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * {@link TranslationSpec} describing the source data to be translated.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getSourceSpec() {
+ return mSourceSpec;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getTargetSpec() {
+ return mTargetSpec;
+ }
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ @DataClass.Generated.Member
+ public @TranslationFlag int getTranslationFlags() {
+ return mTranslationFlags;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationContext { " +
+ "sourceSpec = " + mSourceSpec + ", " +
+ "targetSpec = " + mTargetSpec + ", " +
+ "translationFlags = " + translationFlagToString(mTranslationFlags) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mSourceSpec, flags);
+ dest.writeTypedObject(mTargetSpec, flags);
+ dest.writeInt(mTranslationFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationContext(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ int translationFlags = in.readInt();
+
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mTranslationFlags = translationFlags;
+
+ com.android.internal.util.Preconditions.checkFlagsArgument(
+ mTranslationFlags,
+ FLAG_LOW_LATENCY
+ | FLAG_TRANSLITERATION
+ | FLAG_DICTIONARY_DESCRIPTION);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationContext> CREATOR
+ = new Parcelable.Creator<TranslationContext>() {
+ @Override
+ public TranslationContext[] newArray(int size) {
+ return new TranslationContext[size];
+ }
+
+ @Override
+ public TranslationContext createFromParcel(@NonNull android.os.Parcel in) {
+ return new TranslationContext(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TranslationContext}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder extends BaseBuilder {
+
+ private @NonNull TranslationSpec mSourceSpec;
+ private @NonNull TranslationSpec mTargetSpec;
+ private @TranslationFlag int mTranslationFlags;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param sourceSpec
+ * {@link TranslationSpec} describing the source data to be translated.
+ * @param targetSpec
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ public Builder(
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec) {
+ mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ }
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTranslationFlags(@TranslationFlag int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTranslationFlags = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TranslationContext build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTranslationFlags = defaultTranslationFlags();
+ }
+ TranslationContext o = new TranslationContext(
+ mSourceSpec,
+ mTargetSpec,
+ mTranslationFlags);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1616199021789L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationContext.java",
+ inputSignatures = "public static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_LOW_LATENCY\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_TRANSLITERATION\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_DICTIONARY_DESCRIPTION\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mTranslationFlags\nprivate static int defaultTranslationFlags()\nclass TranslationContext extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 6554e1a..66b45f3 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -21,24 +21,27 @@
import android.annotation.RequiresFeature;
import android.annotation.SystemService;
import android.annotation.WorkerThread;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
-import android.service.translation.TranslationService;
+import android.os.SynchronousResultReceiver;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.SyncResultReceiver;
+import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
import java.util.Objects;
import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -55,9 +58,9 @@
private static final String TAG = "TranslationManager";
/**
- * Timeout for calls to system_server.
+ * Timeout for calls to system_server, default 1 minute.
*/
- static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+ static final int SYNC_CALLS_TIMEOUT_MS = 60_000;
/**
* The result code from result receiver success.
* @hide
@@ -69,6 +72,17 @@
*/
public static final int STATUS_SYNC_CALL_FAIL = 2;
+ /**
+ * Name of the extra used to pass the translation capabilities.
+ * @hide
+ */
+ public static final String EXTRA_CAPABILITIES = "translation_capabilities";
+
+ // TODO: implement update listeners and propagate updates.
+ @GuardedBy("mLock")
+ private final ArrayMap<Pair<Integer, Integer>, ArrayList<PendingIntent>>
+ mTranslationCapabilityUpdateListeners = new ArrayMap<>();
+
private static final Random ID_GENERATOR = new Random();
private final Object mLock = new Object();
@@ -87,7 +101,7 @@
@NonNull
@GuardedBy("mLock")
- private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Integer> mTranslatorIds =
+ private final ArrayMap<TranslationContext, Integer> mTranslatorIds =
new ArrayMap<>();
@NonNull
@@ -110,23 +124,20 @@
*
* <p><strong>NOTE: </strong>Call on a worker thread.
*
- * @param sourceSpec {@link TranslationSpec} for the data to be translated.
- * @param destSpec {@link TranslationSpec} for the translated data.
+ * @param translationContext {@link TranslationContext} containing the specs for creating the
+ * Translator.
* @return a {@link Translator} to be used for calling translation APIs.
*/
@Nullable
@WorkerThread
- public Translator createTranslator(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec) {
- Objects.requireNonNull(sourceSpec, "sourceSpec cannot be null");
- Objects.requireNonNull(sourceSpec, "destSpec cannot be null");
+ public Translator createTranslator(@NonNull TranslationContext translationContext) {
+ Objects.requireNonNull(translationContext, "translationContext cannot be null");
synchronized (mLock) {
// TODO(b/176464808): Disallow multiple Translator now, it will throw
// IllegalStateException. Need to discuss if we can allow multiple Translators.
- final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
- if (mTranslatorIds.containsKey(specs)) {
- return mTranslators.get(mTranslatorIds.get(specs));
+ if (mTranslatorIds.containsKey(translationContext)) {
+ return mTranslators.get(mTranslatorIds.get(translationContext));
}
int translatorId;
@@ -134,7 +145,7 @@
translatorId = Math.abs(ID_GENERATOR.nextInt());
} while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
- final Translator newTranslator = new Translator(mContext, sourceSpec, destSpec,
+ final Translator newTranslator = new Translator(mContext, translationContext,
translatorId, this, mHandler, mService);
// Start the Translator session and wait for the result
newTranslator.start();
@@ -143,7 +154,7 @@
return null;
}
mTranslators.put(translatorId, newTranslator);
- mTranslatorIds.put(specs, translatorId);
+ mTranslatorIds.put(translationContext, translatorId);
return newTranslator;
} catch (Translator.ServiceBinderReceiver.TimeoutException e) {
// TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor
@@ -155,32 +166,103 @@
}
/**
- * Returns a list of locales supported by the {@link TranslationService}.
+ * Returns a set of {@link TranslationCapability}s describing the capabilities for
+ * {@link Translator}s.
+ *
+ * <p>These translation capabilities contains a source and target {@link TranslationSpec}
+ * representing the data expected for both ends of translation process. The capabilities
+ * provides the information and limitations for generating a {@link TranslationContext}.
+ * The context object can then be used by {@link #createTranslator(TranslationContext)} to
+ * obtain a {@link Translator} for translations.</p>
*
* <p><strong>NOTE: </strong>Call on a worker thread.
*
- * TODO: Change to correct language/locale format
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @return A set of {@link TranslationCapability}s.
*/
@NonNull
@WorkerThread
- public List<String> getSupportedLocales() {
+ public Set<TranslationCapability> getTranslationCapabilities(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat) {
try {
- // TODO: implement it
- final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
- mService.getSupportedLocales(receiver, mContext.getUserId());
- int resutCode = receiver.getIntResult();
- if (resutCode != STATUS_SYNC_CALL_SUCCESS) {
- return Collections.emptyList();
+ final SynchronousResultReceiver receiver = new SynchronousResultReceiver();
+ mService.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, receiver,
+ mContext.getUserId());
+ final SynchronousResultReceiver.Result result =
+ receiver.awaitResult(SYNC_CALLS_TIMEOUT_MS);
+ if (result.resultCode != STATUS_SYNC_CALL_SUCCESS) {
+ return Collections.emptySet();
}
- return receiver.getParcelableResult();
+ return new ArraySet<>(
+ (TranslationCapability[]) result.bundle.getParcelableArray(EXTRA_CAPABILITIES));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } catch (SyncResultReceiver.TimeoutException e) {
- Log.e(TAG, "Timed out getting supported locales: " + e);
- return Collections.emptyList();
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timed out getting supported translation capabilities: " + e);
+ return Collections.emptySet();
}
}
+ /**
+ * Registers a {@link PendingIntent} to listen for updates on states of
+ * {@link TranslationCapability}s.
+ *
+ * <p>IMPORTANT: the pending intent must be called to start a service, or a broadcast if it is
+ * an explicit intent.</p>
+ *
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @param pendingIntent the pending intent to invoke when updates are received.
+ */
+ public void addTranslationCapabilityUpdateListener(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+ synchronized (mLock) {
+ final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+ mTranslationCapabilityUpdateListeners.computeIfAbsent(formatPair,
+ (formats) -> new ArrayList<>()).add(pendingIntent);
+ }
+ }
+
+ /**
+ * Unregisters a {@link PendingIntent} to listen for updates on states of
+ * {@link TranslationCapability}s.
+ *
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @param pendingIntent the pending intent to unregister
+ */
+ public void removeTranslationCapabilityUpdateListener(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+ synchronized (mLock) {
+ final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+ if (mTranslationCapabilityUpdateListeners.containsKey(formatPair)) {
+ final ArrayList<PendingIntent> intents =
+ mTranslationCapabilityUpdateListeners.get(formatPair);
+ if (intents.contains(pendingIntent)) {
+ intents.remove(pendingIntent);
+ } else {
+ Log.w(TAG, "pending intent=" + pendingIntent + " does not exist in "
+ + "mTranslationCapabilityUpdateListeners");
+ }
+ } else {
+ Log.w(TAG, "format pair=" + formatPair + " does not exist in "
+ + "mTranslationCapabilityUpdateListeners");
+ }
+ }
+ }
+
+ //TODO: Add method to propagate updates to mTCapabilityUpdateListeners
+
void removeTranslator(int id) {
synchronized (mLock) {
mTranslators.remove(id);
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 0cc26e1..6b26e06 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -44,7 +44,7 @@
import java.util.function.Consumer;
/**
- * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
+ * The {@link Translator} for translation, defined by a {@link TranslationContext}.
*/
@SuppressLint("NotCloseable")
public class Translator {
@@ -62,10 +62,7 @@
private final Context mContext;
@NonNull
- private final TranslationSpec mSourceSpec;
-
- @NonNull
- private final TranslationSpec mDestSpec;
+ private final TranslationContext mTranslationContext;
@NonNull
private final TranslationManager mManager;
@@ -164,13 +161,11 @@
* @hide
*/
public Translator(@NonNull Context context,
- @NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId,
+ @NonNull TranslationContext translationContext, int sessionId,
@NonNull TranslationManager translationManager, @NonNull Handler handler,
@Nullable ITranslationManager systemServerBinder) {
mContext = context;
- mSourceSpec = sourceSpec;
- mDestSpec = destSpec;
+ mTranslationContext = translationContext;
mId = sessionId;
mManager = translationManager;
mHandler = handler;
@@ -183,7 +178,7 @@
*/
void start() {
try {
- mSystemServerBinder.onSessionCreated(mSourceSpec, mDestSpec, mId,
+ mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
mServiceBinderReceiver, mContext.getUserId());
} catch (RemoteException e) {
Log.w(TAG, "RemoteException calling startSession(): " + e);
@@ -223,8 +218,7 @@
/** @hide */
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
- pw.print(prefix); pw.print("sourceSpec: "); pw.println(mSourceSpec);
- pw.print(prefix); pw.print("destSpec: "); pw.println(mDestSpec);
+ pw.print(prefix); pw.print("translationContext: "); pw.println(mTranslationContext);
}
/**
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 7f934c0..d79ecca 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -376,15 +376,17 @@
}
private Translator createTranslatorIfNeeded(
- TranslationSpec sourceSpec, TranslationSpec destSpec) {
+ TranslationSpec sourceSpec, TranslationSpec targetSpec) {
final TranslationManager tm = mContext.getSystemService(TranslationManager.class);
if (tm == null) {
Log.e(TAG, "Can not find TranslationManager when trying to create translator.");
return null;
}
- final Translator translator = tm.createTranslator(sourceSpec, destSpec);
+ final TranslationContext translationContext = new TranslationContext(sourceSpec,
+ targetSpec, /* translationFlags= */ 0);
+ final Translator translator = tm.createTranslator(translationContext);
if (translator != null) {
- final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
+ final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, targetSpec);
mTranslators.put(specs, translator);
}
return translator;
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 58dfc0d..34ad659 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -640,6 +640,12 @@
mWidth,
mHeight
);
+ } else {
+ // This is TYPE_STRETCH and drawing into a Canvas that isn't a Recording Canvas,
+ // so no effect can be shown. Just end the effect.
+ mState = STATE_IDLE;
+ mDistance = 0;
+ mVelocity = 0;
}
boolean oneLastFrame = false;
@@ -771,8 +777,9 @@
* considered at rest or false if it is still animating.
*/
private boolean isAtEquilibrium() {
- double displacement = mDistance * mHeight; // in pixels
- return Math.abs(mVelocity) < VELOCITY_THRESHOLD
+ double displacement = mDistance * mHeight * LINEAR_STRETCH_INTENSITY; // in pixels
+ double velocity = mVelocity * LINEAR_STRETCH_INTENSITY;
+ return Math.abs(velocity) < VELOCITY_THRESHOLD
&& Math.abs(displacement) < VALUE_THRESHOLD;
}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 3709aa1..9789d70 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -15,6 +15,7 @@
*/
package android.window;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import android.annotation.ColorInt;
@@ -32,6 +33,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -183,6 +185,7 @@
* Create SplashScreenWindowView object from materials.
*/
public SplashScreenView build() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#build");
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
final SplashScreenView view = (SplashScreenView)
layoutInflater.inflate(R.layout.splash_screen_view, null, false);
@@ -208,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;
@@ -226,6 +229,7 @@
+ view.mBrandingImageView + " drawable: " + mBrandingDrawable
+ " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return view;
}
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 02cbccc..a5b894d 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -130,12 +130,6 @@
// Flags related to media notifications
/**
- * (boolean) If {@code true}, enables the seekbar in compact media notifications.
- */
- public static final String COMPACT_MEDIA_SEEKBAR_ENABLED =
- "compact_media_notification_seekbar_enabled";
-
- /**
* (int) Maximum number of days to retain the salt for hashing direct share targets in logging
*/
public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days";
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 7f87885..a043756 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -89,6 +89,7 @@
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
@@ -12548,81 +12549,6 @@
}
/**
- * SparseDoubleArray map integers to doubles.
- * Its implementation is the same as that of {@link SparseLongArray}; see there for details.
- *
- * @see SparseLongArray
- */
- private static class SparseDoubleArray {
- /**
- * The int->double map, but storing the doubles as longs using
- * {@link Double.doubleToRawLongBits(double)}.
- */
- private final SparseLongArray mValues = new SparseLongArray();
-
- /**
- * Gets the double mapped from the specified key, or <code>0</code>
- * if no such mapping has been made.
- */
- public double get(int key) {
- if (mValues.indexOfKey(key) >= 0) {
- return Double.longBitsToDouble(mValues.get(key));
- }
- return 0;
- }
-
- /**
- * Adds a mapping from the specified key to the specified value,
- * replacing the previous mapping from the specified key if there
- * was one.
- */
- public void put(int key, double value) {
- mValues.put(key, Double.doubleToRawLongBits(value));
- }
-
- /**
- * Adds a mapping from the specified key to the specified value,
- * <b>adding</b> to the previous mapping from the specified key if there
- * was one.
- */
- public void add(int key, double summand) {
- final double oldValue = get(key);
- put(key, oldValue + summand);
- }
-
- /**
- * Returns the number of key-value mappings that this SparseDoubleArray
- * currently stores.
- */
- public int size() {
- return mValues.size();
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the key from the <code>index</code>th key-value mapping that this
- * SparseDoubleArray stores.
- *
- * @see SparseLongArray#keyAt(int)
- */
- public int keyAt(int index) {
- return mValues.keyAt(index);
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the value from the <code>index</code>th key-value mapping that this
- * SparseDoubleArray stores.
- *
- * @see SparseLongArray#valueAt(int)
- */
- public double valueAt(int index) {
- return Double.longBitsToDouble(mValues.valueAt(index));
- }
-
- }
-
- /**
* Read and record Rail Energy data.
*/
public void updateRailStatsLocked() {
diff --git a/core/java/com/android/internal/widget/MediaNotificationView.java b/core/java/com/android/internal/widget/MediaNotificationView.java
index f42d5da..8ff3c10 100644
--- a/core/java/com/android/internal/widget/MediaNotificationView.java
+++ b/core/java/com/android/internal/widget/MediaNotificationView.java
@@ -19,31 +19,19 @@
import android.annotation.Nullable;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.NotificationHeaderView;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.RemoteViews;
import java.util.ArrayList;
/**
- * A TextView that can float around an image on the end.
+ * The Layout class which handles template details for the Notification.MediaStyle
*
* @hide
*/
@RemoteViews.RemoteView
public class MediaNotificationView extends FrameLayout {
- private final int mNotificationContentMarginEnd;
- private final int mNotificationContentImageMarginEnd;
- private ImageView mRightIcon;
- private View mActions;
- private NotificationHeaderView mHeader;
- private View mMainColumn;
- private View mMediaContent;
- private int mImagePushIn;
private ArrayList<VisibilityChangeListener> mListeners;
public MediaNotificationView(Context context) {
@@ -58,120 +46,14 @@
this(context, attrs, defStyleAttr, 0);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- boolean hasIcon = mRightIcon.getVisibility() != GONE;
- if (!hasIcon) {
- resetHeaderIndention();
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int mode = MeasureSpec.getMode(widthMeasureSpec);
- boolean reMeasure = false;
- mImagePushIn = 0;
- if (hasIcon && mode != MeasureSpec.UNSPECIFIED) {
- int size = MeasureSpec.getSize(widthMeasureSpec);
- size = size - mActions.getMeasuredWidth();
- ViewGroup.MarginLayoutParams layoutParams =
- (MarginLayoutParams) mRightIcon.getLayoutParams();
- int imageEndMargin = layoutParams.getMarginEnd();
- size -= imageEndMargin;
- int fullHeight = mMediaContent.getMeasuredHeight();
- if (size > fullHeight) {
- size = fullHeight;
- } else if (size < fullHeight) {
- size = Math.max(0, size);
- mImagePushIn = fullHeight - size;
- }
- if (layoutParams.width != fullHeight || layoutParams.height != fullHeight) {
- layoutParams.width = fullHeight;
- layoutParams.height = fullHeight;
- mRightIcon.setLayoutParams(layoutParams);
- reMeasure = true;
- }
-
- // lets ensure that the main column doesn't run into the image
- ViewGroup.MarginLayoutParams params
- = (MarginLayoutParams) mMainColumn.getLayoutParams();
- int marginEnd = size + imageEndMargin + mNotificationContentMarginEnd;
- if (marginEnd != params.getMarginEnd()) {
- params.setMarginEnd(marginEnd);
- mMainColumn.setLayoutParams(params);
- reMeasure = true;
- }
- // TODO(b/172652345): validate all this logic (especially positioning of expand button)
- // margin for the entire header line
- int headerMarginEnd = imageEndMargin;
- // margin for the header text (not including the expand button and other icons)
- int headerExtraMarginEnd = Math.max(0,
- size + imageEndMargin - mHeader.getTopLineBaseMarginEnd());
- if (headerExtraMarginEnd != mHeader.getTopLineExtraMarginEnd()) {
- mHeader.setTopLineExtraMarginEnd(headerExtraMarginEnd);
- reMeasure = true;
- }
- params = (MarginLayoutParams) mHeader.getLayoutParams();
- if (params.getMarginEnd() != headerMarginEnd) {
- params.setMarginEnd(headerMarginEnd);
- mHeader.setLayoutParams(params);
- reMeasure = true;
- }
- if (mHeader.getPaddingEnd() != mNotificationContentImageMarginEnd) {
- mHeader.setPaddingRelative(mHeader.getPaddingStart(),
- mHeader.getPaddingTop(),
- mNotificationContentImageMarginEnd,
- mHeader.getPaddingBottom());
- reMeasure = true;
- }
- }
- if (reMeasure) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mImagePushIn > 0) {
- if (this.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- mImagePushIn *= -1;
- }
- mRightIcon.layout(mRightIcon.getLeft() + mImagePushIn, mRightIcon.getTop(),
- mRightIcon.getRight() + mImagePushIn, mRightIcon.getBottom());
- }
- }
-
- private void resetHeaderIndention() {
- if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) {
- mHeader.setPaddingRelative(mHeader.getPaddingStart(),
- mHeader.getPaddingTop(),
- mNotificationContentMarginEnd,
- mHeader.getPaddingBottom());
- }
- ViewGroup.MarginLayoutParams headerParams =
- (MarginLayoutParams) mHeader.getLayoutParams();
- headerParams.setMarginEnd(0);
- if (headerParams.getMarginEnd() != 0) {
- headerParams.setMarginEnd(0);
- mHeader.setLayoutParams(headerParams);
- }
- }
-
public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mNotificationContentMarginEnd = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_end);
- mNotificationContentImageMarginEnd = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_image_margin_end);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mRightIcon = findViewById(com.android.internal.R.id.right_icon);
- mActions = findViewById(com.android.internal.R.id.media_actions);
- mHeader = findViewById(com.android.internal.R.id.notification_header);
- mMainColumn = findViewById(com.android.internal.R.id.notification_main_column);
- mMediaContent = findViewById(com.android.internal.R.id.notification_media_content);
}
@Override
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_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index b790056..b46b5a2 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -68,7 +68,7 @@
};
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
- jlong width, jlong height, jint format, jboolean enableTripleBuffering) {
+ jlong width, jlong height, jint format) {
String8 str8;
if (jName) {
const jchar* str16 = env->GetStringCritical(jName, nullptr);
@@ -81,7 +81,7 @@
std::string name = str8.string();
sp<BLASTBufferQueue> queue =
new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
- height, format, enableTripleBuffering);
+ height, format);
queue->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(queue.get());
}
@@ -140,7 +140,7 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
- {"nativeCreate", "(Ljava/lang/String;JJJIZ)J", (void*)nativeCreate},
+ {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
{"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
{"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction},
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..16c3ab0 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -21,40 +21,38 @@
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;
}
-message PrebakedProto {
+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;
}
// 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 480e1de..4157af8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3578,7 +3578,7 @@
@hide
-->
<permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|recents" />
<!-- Allows an application to retrieve the current state of keys and
switches.
diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index dd79a0b..5f1b60e 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -24,7 +24,6 @@
android:paddingTop="8dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
- android:layout_marginEnd="2dp"
android:gravity="center"
android:background="@drawable/notification_material_media_action_background"
android:visibility="gone"
diff --git a/core/res/res/layout/notification_material_media_seekbar.xml b/core/res/res/layout/notification_material_media_seekbar.xml
deleted file mode 100644
index 4aa8acc..0000000
--- a/core/res/res/layout/notification_material_media_seekbar.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/notification_media_progress"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_alignParentBottom="true"
- >
- <SeekBar android:id="@+id/notification_media_progress_bar"
- style="@style/Widget.ProgressBar.Horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxHeight="3dp"
- android:paddingTop="24dp"
- android:paddingBottom="24dp"
- android:clickable="true"
- android:layout_marginBottom="-24dp"
- android:layout_marginTop="-12dp"
- android:splitTrack="false"
- />
- <FrameLayout
- android:id="@+id/notification_media_progress_time"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginBottom="11dp"
- >
-
- <!-- width is set to "match_parent" to avoid extra layout calls -->
- <TextView android:id="@+id/notification_media_elapsed_time"
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:gravity="start"
- />
-
- <TextView android:id="@+id/notification_media_total_time"
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:gravity="end"
- />
- </FrameLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index bad9a6b..e644cd5 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -46,20 +46,6 @@
android:padding="@dimen/notification_icon_circle_padding"
/>
- <ImageView
- android:id="@+id/right_icon"
- android:layout_width="@dimen/notification_right_icon_size"
- android:layout_height="@dimen/notification_right_icon_size"
- android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginEnd="@dimen/notification_header_expand_icon_size"
- android:background="@drawable/notification_large_icon_outline"
- android:clipToOutline="true"
- android:importantForAccessibility="no"
- android:scaleType="centerCrop"
- />
-
<FrameLayout
android:id="@+id/alternate_expand_target"
android:layout_width="@dimen/notification_content_margin_start"
@@ -68,95 +54,116 @@
android:importantForAccessibility="no"
/>
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="end">
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
- />
-
- </FrameLayout>
-
<LinearLayout
- android:id="@+id/notification_headerless_view_column"
+ android:id="@+id/notification_headerless_view_row"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
- android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
- android:orientation="vertical"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
>
- <!-- extends ViewGroup -->
- <NotificationTopLineView
- android:id="@+id/notification_top_line"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_headerless_line_height"
- android:layout_marginEnd="@dimen/notification_heading_margin_end"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:clipChildren="false"
- android:theme="@style/Theme.DeviceDefault.Notification"
- >
-
- <!--
- NOTE: The notification_top_line_views layout contains the app_name_text.
- In order to include the title view at the beginning, the Notification.Builder
- has logic to hide that view whenever this title view is to be visible.
- -->
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:singleLine="true"
- android:textAlignment="viewStart"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- />
-
- <include layout="@layout/notification_top_line_views" />
-
- </NotificationTopLineView>
-
<LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_heading_margin_end"
- android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
android:orientation="vertical"
>
- <com.android.internal.widget.NotificationVanishingFrameLayout
- android:layout_width="match_parent"
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
>
- <!-- This is the simplest way to keep this text vertically centered without using
- gravity="center_vertical" which causes jumpiness in expansion animations. -->
- <include
- layout="@layout/notification_template_text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="center_vertical"
- android:layout_marginTop="0dp"
- />
- </com.android.internal.widget.NotificationVanishingFrameLayout>
- <include
- layout="@layout/notification_template_progress"
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_headerless_line_height"
- />
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <com.android.internal.widget.NotificationVanishingFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ />
+
+ </LinearLayout>
</LinearLayout>
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
</LinearLayout>
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index aa20ad3..ff64315 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -20,17 +20,8 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="#00000000"
android:tag="bigMediaNarrow"
>
- <!-- The size will actually be determined at runtime -->
- <ImageView
- android:id="@+id/right_icon"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_gravity="top|end"
- android:scaleType="centerCrop"
- />
<include
layout="@layout/notification_template_header"
@@ -49,46 +40,27 @@
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="46dp"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginBottom="@dimen/notification_content_margin"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
>
- <!-- TODO(b/172652345): fix the media style -->
- <!--<include layout="@layout/notification_template_part_line1"/>-->
- <!--<include layout="@layout/notification_template_text"/>-->
-
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart"
- />
-
- <com.android.internal.widget.ImageFloatingTextView
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="top"
- android:layout_marginTop="0.5dp"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:gravity="top"
- android:singleLine="true"
- android:textAlignment="viewStart"
- />
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_text"/>
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-21dp"
+ android:paddingStart="44dp"
+ android:paddingEnd="44dp"
+ android:paddingBottom="@dimen/media_notification_actions_padding_bottom"
+ android:gravity="top"
android:orientation="horizontal"
android:layoutDirection="ltr"
- style="@style/NotificationMediaActionContainer"
>
<include
@@ -117,10 +89,8 @@
/>
</LinearLayout>
- <ViewStub
- android:id="@+id/notification_media_seekbar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
</LinearLayout>
+
+ <include layout="@layout/notification_template_right_icon" />
+
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 542e59d..2991b17 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -19,101 +19,173 @@
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#00000000"
+ android:layout_height="@dimen/notification_min_height"
android:tag="media"
>
- <ImageView android:id="@+id/right_icon"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:adjustViewBounds="true"
- android:layout_gravity="top|end"
+
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_left_icon_size"
+ android:layout_height="@dimen/notification_left_icon_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
android:scaleType="centerCrop"
- />
- <include layout="@layout/notification_template_header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/media_notification_header_height"
+ android:visibility="gone"
/>
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ />
+
<LinearLayout
+ android:id="@+id/notification_headerless_view_row"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:id="@+id/notification_media_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
>
+
<LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:layout_marginTop="46dp"
- android:layout_alignParentTop="true"
- android:tag="media"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:orientation="vertical"
>
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
<LinearLayout
- android:id="@+id/notification_content_container"
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="fill_vertical"
- android:layout_weight="1"
- android:paddingBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
- <!-- TODO(b/172652345): fix the media style -->
- <!--<include layout="@layout/notification_template_part_line1"/>-->
- <!--<include layout="@layout/notification_template_text"/>-->
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ <com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
/>
- <com.android.internal.widget.ImageFloatingTextView
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="top"
- android:layout_marginTop="0.5dp"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:gravity="top"
- android:singleLine="true"
- android:textAlignment="viewStart"
- />
</LinearLayout>
- <LinearLayout
- android:id="@+id/media_actions"
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layoutDirection="ltr"
+ android:orientation="horizontal"
+ >
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action0"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action1"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action2"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|end"
- android:layout_marginStart="10dp"
- android:layoutDirection="ltr"
- android:orientation="horizontal"
- >
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action0"
+ android:layout_gravity="center_vertical|end"
/>
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action1"
- />
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action2"
- />
- </LinearLayout>
- </LinearLayout>
- <ViewStub android:id="@+id/notification_media_seekbar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- />
+
+ </FrameLayout>
+
</LinearLayout>
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 91896fe..748e3b1 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -249,34 +249,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 +286,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 +323,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/config.xml b/core/res/res/values/config.xml
index b4ef63f..a3b3bb5 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
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 43dbd38..afbbe46 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -226,9 +226,6 @@
<!-- The margin on the end of the top-line content views (accommodates the expander) -->
<dimen name="notification_heading_margin_end">56dp</dimen>
- <!-- The margin for text at the end of the image view for media notifications -->
- <dimen name="notification_media_image_margin_end">72dp</dimen>
-
<!-- The height of the notification action list -->
<dimen name="notification_action_list_height">60dp</dimen>
@@ -345,6 +342,9 @@
<!-- The minimum width of the app name in the header if it shrinks -->
<dimen name="notification_header_shrink_min_width">72dp</dimen>
+ <!-- The minimum width of optional header fields below which the view is simply hidden -->
+ <dimen name="notification_header_shrink_hide_width">24sp</dimen>
+
<!-- The size of the media actions in the media notification. -->
<dimen name="media_notification_action_button_size">48dp</dimen>
@@ -360,9 +360,6 @@
<!-- The absolute height for the header in a media notification. -->
<dimen name="media_notification_header_height">@dimen/notification_header_height</dimen>
- <!-- The margin of the content to an image-->
- <dimen name="notification_content_image_margin_end">8dp</dimen>
-
<!-- The padding at the end of actions when the snooze and bubble buttons are gone-->
<dimen name="snooze_and_bubble_gone_padding_end">12dp</dimen>
@@ -483,9 +480,6 @@
<!-- Top padding for notification when text is large and narrow (i.e. it has 3 lines -->
<dimen name="notification_top_pad_large_text_narrow">-4dp</dimen>
- <!-- Padding for notification icon when drawn with circle around it -->
- <dimen name="notification_large_icon_circle_padding">11dp</dimen>
-
<!-- The margin on top of the text of the notification -->
<dimen name="notification_text_margin_top">6dp</dimen>
@@ -736,12 +730,10 @@
<dimen name="notification_big_picture_max_height">284dp</dimen>
<!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. This value is determined by the standard panel size -->
<dimen name="notification_big_picture_max_width">416dp</dimen>
- <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. This value is determined by the expanded media template-->
- <dimen name="notification_media_image_max_height">140dp</dimen>
- <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
- <dimen name="notification_media_image_max_width">280dp</dimen>
<!-- The size of the right icon -->
<dimen name="notification_right_icon_size">48dp</dimen>
+ <!-- The margin between the right icon and the content. -->
+ <dimen name="notification_right_icon_content_margin">12dp</dimen>
<!-- The top and bottom margin of the right icon in the normal notification states -->
<dimen name="notification_right_icon_headerless_margin">20dp</dimen>
<!-- The top margin of the right icon in the "big" notification states -->
@@ -762,10 +754,6 @@
<dimen name="notification_big_picture_max_height_low_ram">208dp</dimen>
<!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. -->
<dimen name="notification_big_picture_max_width_low_ram">294dp</dimen>
- <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. -->
- <dimen name="notification_media_image_max_height_low_ram">100dp</dimen>
- <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
- <dimen name="notification_media_image_max_width_low_ram">100dp</dimen>
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen>
<!-- The maximum size of the grayscale icon -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c7ded0c..fbf67e0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1481,17 +1481,6 @@
<item name="android:windowExitAnimation">@anim/slide_out_down</item>
</style>
- <!-- The style for the container of media actions in a notification. -->
- <!-- @hide -->
- <style name="NotificationMediaActionContainer">
- <item name="layout_width">wrap_content</item>
- <item name="layout_height">wrap_content</item>
- <item name="layout_marginTop">-21dp</item>
- <item name="paddingStart">8dp</item>
- <item name="paddingBottom">@dimen/media_notification_actions_padding_bottom</item>
- <item name="gravity">top</item>
- </style>
-
<!-- The style for normal action button on notification -->
<style name="NotificationAction" parent="Widget.Material.Light.Button.Borderless.Small">
<item name="textColor">@color/notification_action_button_text_color</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1d74d85..d6a6f4d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -200,12 +200,7 @@
<java-symbol type="id" name="action2" />
<java-symbol type="id" name="action3" />
<java-symbol type="id" name="action4" />
- <java-symbol type="id" name="notification_media_seekbar_container" />
<java-symbol type="id" name="notification_media_content" />
- <java-symbol type="id" name="notification_media_progress" />
- <java-symbol type="id" name="notification_media_progress_bar" />
- <java-symbol type="id" name="notification_media_elapsed_time" />
- <java-symbol type="id" name="notification_media_total_time" />
<java-symbol type="id" name="big_picture" />
<java-symbol type="id" name="big_text" />
<java-symbol type="id" name="chronometer" />
@@ -525,7 +520,6 @@
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
<java-symbol type="dimen" name="notification_top_pad_large_text_narrow" />
- <java-symbol type="dimen" name="notification_large_icon_circle_padding" />
<java-symbol type="dimen" name="notification_badge_size" />
<java-symbol type="dimen" name="immersive_mode_cling_width" />
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
@@ -1564,7 +1558,6 @@
<java-symbol type="layout" name="immersive_mode_cling" />
<java-symbol type="layout" name="user_switching_dialog" />
<java-symbol type="layout" name="common_tab_settings" />
- <java-symbol type="layout" name="notification_material_media_seekbar" />
<java-symbol type="layout" name="resolver_list_per_profile" />
<java-symbol type="layout" name="chooser_list_per_profile" />
<java-symbol type="layout" name="resolver_empty_states" />
@@ -2921,6 +2914,7 @@
<java-symbol type="drawable" name="ic_expand_bundle" />
<java-symbol type="drawable" name="ic_collapse_bundle" />
<java-symbol type="dimen" name="notification_header_shrink_min_width" />
+ <java-symbol type="dimen" name="notification_header_shrink_hide_width" />
<java-symbol type="dimen" name="notification_content_margin_start" />
<java-symbol type="dimen" name="notification_content_margin_end" />
<java-symbol type="dimen" name="notification_heading_margin_end" />
@@ -3010,7 +3004,6 @@
<java-symbol type="string" name="new_sms_notification_content" />
<java-symbol type="dimen" name="media_notification_expanded_image_margin_bottom" />
- <java-symbol type="dimen" name="notification_content_image_margin_end" />
<java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
@@ -3019,8 +3012,6 @@
<java-symbol type="id" name="aerr_wait" />
- <java-symbol type="id" name="notification_content_container" />
-
<java-symbol type="plurals" name="duration_minutes_shortest" />
<java-symbol type="plurals" name="duration_hours_shortest" />
<java-symbol type="plurals" name="duration_days_shortest" />
@@ -3138,7 +3129,6 @@
<java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
- <java-symbol type="dimen" name="notification_media_image_margin_end" />
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_action_disabled_alpha" />
<java-symbol type="id" name="tag_margin_end_when_icon_visible" />
@@ -3461,17 +3451,14 @@
<java-symbol type="dimen" name="notification_big_picture_max_height"/>
<java-symbol type="dimen" name="notification_big_picture_max_width"/>
- <java-symbol type="dimen" name="notification_media_image_max_width"/>
- <java-symbol type="dimen" name="notification_media_image_max_height"/>
<java-symbol type="dimen" name="notification_right_icon_size"/>
+ <java-symbol type="dimen" name="notification_right_icon_content_margin"/>
<java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
<java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
<java-symbol type="dimen" name="notification_big_picture_max_width_low_ram"/>
- <java-symbol type="dimen" name="notification_media_image_max_width_low_ram"/>
- <java-symbol type="dimen" name="notification_media_image_max_height_low_ram"/>
<java-symbol type="dimen" name="notification_right_icon_size_low_ram"/>
<java-symbol type="dimen" name="notification_grayscale_icon_max_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height_low_ram"/>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 252938a..0ea6364 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,8 +16,6 @@
package android.app;
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -99,23 +97,6 @@
}
@Test
- public void testColorSatisfiedWhenBgDarkTextDarker() {
- Notification.Builder builder = getMediaNotification();
- Notification n = builder.build();
-
- assertTrue(n.isColorized());
-
- // An initial guess where the foreground color is actually darker than an already dark bg
- int backgroundColor = 0xff585868;
- int initialForegroundColor = 0xff505868;
- builder.setColorPalette(backgroundColor, initialForegroundColor);
- int primaryTextColor = builder.getPrimaryTextColor(builder.mParams);
- assertTrue(satisfiesTextContrast(primaryTextColor, backgroundColor));
- int secondaryTextColor = builder.getSecondaryTextColor(builder.mParams);
- assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor));
- }
-
- @Test
public void testHasCompletedProgress_noProgress() {
Notification n = new Notification.Builder(mContext).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..242adab 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() {
@@ -169,250 +158,106 @@
}
@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));
+ }
+
+ @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));
+ }
+
+ @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)
.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());
- @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());
}
private Resources mockRingtoneResources() {
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/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
new file mode 100644
index 0000000..188b6c1
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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, /* duration= */ 100);
+
+ assertEquals(100, step.getDuration());
+ assertTrue(step.hasNonZeroAmplitude());
+ assertEquals(1f, step.getAmplitude());
+ }
+
+ @Test
+ public void testSerialization() {
+ StepSegment original = new StepSegment(0.5f, 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, /* duration= */ 100).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(/* amplitude= */ -2, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(/* amplitude= */ 2, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(2, /* duration= */ -1).validate());
+ }
+
+ @Test
+ public void testHasNonZeroAmplitude() {
+ assertTrue(new StepSegment(1f, 0).hasNonZeroAmplitude());
+ assertTrue(new StepSegment(0.01f, 0).hasNonZeroAmplitude());
+ assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0).hasNonZeroAmplitude());
+ assertFalse(new StepSegment(0, 0).hasNonZeroAmplitude());
+ }
+
+ @Test
+ public void testResolve() {
+ StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+ assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude());
+ assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE);
+
+ StepSegment resolved = new StepSegment(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);
+ assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ }
+
+ @Test
+ public void testScale_fullAmplitude() {
+ StepSegment initial = new StepSegment(1f, 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);
+
+ 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);
+
+ 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);
+
+ 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/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
new file mode 100644
index 0000000..2dd3f69
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Internal tests for {@link SparseDoubleArray}.
+ *
+ * Run using:
+ * atest FrameworksCoreTests:android.util.SparseDoubleArrayTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseDoubleArrayTest {
+ private static final double EXACT_PRECISION = 0;
+ private static final double PRECISION = 0.000000001;
+
+ @Test
+ public void testPutGet() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+ assertEquals("Array should be empty", 0, sda.size());
+
+ final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+ final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+ Double.NaN,
+ 4311236312.0 / 3431470161413514334123.0,
+ 431123636434132151313412.0 / 34323.0,
+ 0};
+ for (int i = 0; i < keys.length; i++) {
+ sda.put(keys[i], values[i]);
+ }
+
+ assertEquals("Wrong size array", keys.length, sda.size());
+ // Due to the implementation, we actually expect EXACT double equality.
+ for (int i = 0; i < keys.length; i++) {
+ assertEquals("Wrong value at index " + i, values[i], sda.get(keys[i]), EXACT_PRECISION);
+ }
+
+ // Now check something that was never put in
+ assertEquals("Wrong value for absent index", 0, sda.get(100000), EXACT_PRECISION);
+ }
+
+ @Test
+ public void testAdd() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+
+ sda.put(4, 6.1);
+ sda.add(4, -1.2);
+ sda.add(2, -1.2);
+
+ assertEquals(6.1 - 1.2, sda.get(4), PRECISION);
+ assertEquals(-1.2, sda.get(2), PRECISION);
+ }
+
+ @Test
+ public void testKeyValueAt() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+
+ final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+ final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+ Double.NaN,
+ 4311236312.0 / 3431470161413514334123.0,
+ 431123636434132151313412.0 / 34323.0,
+ 0};
+ for (int i = 0; i < keys.length; i++) {
+ sda.put(keys[i], values[i]);
+ }
+
+ // Sort the sample data.
+ final ArrayMap<Integer, Double> map = new ArrayMap<>(keys.length);
+ for (int i = 0; i < keys.length; i++) {
+ map.put(keys[i], values[i]);
+ }
+ final int[] sortedKeys = Arrays.copyOf(keys, keys.length);
+ Arrays.sort(sortedKeys);
+
+ for (int i = 0; i < sortedKeys.length; i++) {
+ final int expectedKey = sortedKeys[i];
+ final double expectedValue = map.get(expectedKey);
+
+ assertEquals("Wrong key at index " + i, expectedKey, sda.keyAt(i), PRECISION);
+ assertEquals("Wrong value at index " + i, expectedValue, sda.valueAt(i), PRECISION);
+ }
+ }
+}
diff --git a/data/etc/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml
index ac16af3..abde232 100644
--- a/data/etc/car/com.android.car.carlauncher.xml
+++ b/data/etc/car/com.android.car.carlauncher.xml
@@ -18,6 +18,7 @@
<privapp-permissions package="com.android.car.carlauncher">
<permission name="android.permission.ACTIVITY_EMBEDDING"/>
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 3aaf11c..4534d36 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -28,7 +28,7 @@
public long mNativeObject; // BLASTBufferQueue*
private static native long nativeCreate(String name, long surfaceControl, long width,
- long height, int format, boolean tripleBufferingEnabled);
+ long height, int format);
private static native void nativeDestroy(long ptr);
private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
private static native void nativeSetNextTransaction(long ptr, long transactionPtr);
@@ -53,9 +53,8 @@
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
- @PixelFormat.Format int format, boolean tripleBufferingEnabled) {
- mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format,
- tripleBufferingEnabled);
+ @PixelFormat.Format int format) {
+ mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
}
public void destroy() {
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index d2b3cf6..6bd0e0a 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.wm.shell">
+ <!-- System permission required by WM Shell Task Organizer. -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 4768cf5..fa94ec5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -126,7 +126,7 @@
*/
public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
return Settings.Secure.getInt(resolver,
- Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1;
+ Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0 /* Default OFF */) == 1;
}
void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
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 3f46fee..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
@@ -16,6 +16,8 @@
package com.android.wm.shell.startingsurface;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.app.ActivityThread;
@@ -31,6 +33,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
+import android.os.Trace;
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.SplashScreenView;
@@ -70,6 +73,7 @@
private int mIconNormalExitDistance;
private int mIconEarlyExitDistance;
private final TransactionPool mTransactionPool;
+ private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
@@ -109,49 +113,82 @@
return new ColorDrawable(getSystemBGColor());
}
- SplashScreenView makeSplashScreenContentView(Context context, int iconRes,
- int splashscreenContentResId) {
- updateDensity();
- // splash screen content will be deprecated after S.
- final SplashScreenView ssc =
- makeSplashscreenContentDrawable(context, splashscreenContentResId);
-
- if (ssc != null) {
- return ssc;
- }
-
- final SplashScreenWindowAttrs attrs =
- SplashScreenWindowAttrs.createWindowAttrs(context);
- final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
+ private @ColorInt int peekWindowBGColor(Context context) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
final Drawable themeBGDrawable;
-
- if (attrs.mWindowBgColor != 0) {
- themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
- } else if (attrs.mWindowBgResId != 0) {
- themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+ if (mTmpAttrs.mWindowBgColor != 0) {
+ themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
+ } 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);
+ return estimatedWindowBGColor;
+ }
+
+ private int estimateWindowBGColor(Drawable themeBGDrawable) {
+ final DrawableColorTester themeBGTester =
+ new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
+ if (themeBGTester.nonTransparentRatio() == 0) {
+ // the window background is transparent, unable to draw
+ Slog.w(TAG, "Window background is transparent, fill background with black color");
+ return getSystemBGColor();
+ } else {
+ return themeBGTester.getDominateColor();
+ }
+ }
+
+ SplashScreenView makeSplashScreenContentView(Context context, int iconRes) {
+ updateDensity();
+
+ getWindowAttrs(context, mTmpAttrs);
+ final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final int animationDuration;
final Drawable iconDrawable;
- if (attrs.mReplaceIcon != null) {
- iconDrawable = attrs.mReplaceIcon;
+ if (mTmpAttrs.mReplaceIcon != null) {
+ iconDrawable = mTmpAttrs.mReplaceIcon;
animationDuration = Math.max(0,
- Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration));
+ Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
} else {
iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
: context.getPackageManager().getDefaultActivityIcon();
animationDuration = 0;
}
+ final int themeBGColor = peekWindowBGColor(context);
// TODO (b/173975965) Tracking the performance on improved splash screen.
return builder
.setContext(context)
- .setThemeDrawable(themeBGDrawable)
+ .setWindowBGColor(themeBGColor)
.setIconDrawable(iconDrawable)
.setIconAnimationDuration(animationDuration)
- .setBrandingDrawable(attrs.mBrandingImage)
- .setIconBackground(attrs.mIconBgColor).build();
+ .setBrandingDrawable(mTmpAttrs.mBrandingImage)
+ .setIconBackground(mTmpAttrs.mIconBgColor).build();
+ }
+
+ private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
+ final TypedArray typedArray = context.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ attrs.mWindowBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+ attrs.mReplaceIcon = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenAnimatedIcon);
+ attrs.mAnimationDuration = typedArray.getInt(
+ R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+ attrs.mBrandingImage = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenBrandingImage);
+ attrs.mIconBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
+ typedArray.recycle();
+ if (DEBUG) {
+ Slog.d(TAG, "window attributes color: "
+ + Integer.toHexString(attrs.mWindowBgColor)
+ + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+ + " brandImage " + attrs.mBrandingImage);
+ }
}
static class SplashScreenWindowAttrs {
@@ -161,35 +198,9 @@
private Drawable mBrandingImage = null;
private int mIconBgColor = Color.TRANSPARENT;
private int mAnimationDuration = 0;
-
- static SplashScreenWindowAttrs createWindowAttrs(Context context) {
- final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
- final TypedArray typedArray = context.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- attrs.mWindowBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
- attrs.mReplaceIcon = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenAnimatedIcon);
- attrs.mAnimationDuration = typedArray.getInt(
- R.styleable.Window_windowSplashScreenAnimationDuration, 0);
- attrs.mBrandingImage = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenBrandingImage);
- attrs.mIconBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
- typedArray.recycle();
- if (DEBUG) {
- Slog.d(TAG, "window attributes color: "
- + Integer.toHexString(attrs.mWindowBgColor)
- + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
- + " brandImage " + attrs.mBrandingImage);
- }
- return attrs;
- }
}
private class StartingWindowViewBuilder {
- private Drawable mThemeBGDrawable;
private Drawable mIconDrawable;
private int mIconAnimationDuration;
private Context mContext;
@@ -203,8 +214,8 @@
private Drawable mFinalIconDrawable;
private float mScale = 1f;
- StartingWindowViewBuilder setThemeDrawable(Drawable background) {
- mThemeBGDrawable = background;
+ StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+ mThemeColor = background;
mBuildComplete = false;
return this;
}
@@ -247,18 +258,14 @@
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
return null;
}
- if (mThemeBGDrawable == null) {
- Slog.w(TAG, "Theme Background Drawable is null, forget to set Theme Drawable?");
- mThemeBGDrawable = createDefaultBackgroundDrawable();
- }
- processThemeColor();
+
if (!processAdaptiveIcon() && mIconDrawable != null) {
if (DEBUG) {
Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
}
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
mIconBackground != Color.TRANSPARENT
- ? mIconBackground : mThemeColor, mIconDrawable);
+ ? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
}
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
@@ -266,22 +273,10 @@
return mCachedResult;
}
- private void createIconDrawable(Drawable iconDrawable) {
+ private void createIconDrawable(Drawable iconDrawable, int iconSize) {
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
mIconBackground != Color.TRANSPARENT
- ? mIconBackground : mThemeColor, iconDrawable);
- }
-
- private void processThemeColor() {
- final DrawableColorTester themeBGTester =
- new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */);
- if (themeBGTester.nonTransparentRatio() == 0) {
- // the window background is transparent, unable to draw
- Slog.w(TAG, "Window background is transparent, fill background with black color");
- mThemeColor = getSystemBGColor();
- } else {
- mThemeColor = themeBGTester.getDominateColor();
- }
+ ? mIconBackground : mThemeColor, iconDrawable, iconSize);
}
private boolean processAdaptiveIcon() {
@@ -289,6 +284,7 @@
return false;
}
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) mIconDrawable;
final DrawableColorTester backIconTester =
new DrawableColorTester(adaptiveIconDrawable.getBackground());
@@ -325,29 +321,28 @@
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
}
- // Using AdaptiveIconDrawable here can help keep the shape consistent with the
- // current settings.
- createIconDrawable(iconForeground);
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
mScale = 1.5f;
}
+ // Using AdaptiveIconDrawable here can help keep the shape consistent with the
+ // current settings.
+ final int iconSize = (int) (0.5f + mIconSize * mScale);
+ createIconDrawable(iconForeground, iconSize);
} else {
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
}
- if (mIconBackground != Color.TRANSPARENT) {
- createIconDrawable(adaptiveIconDrawable);
- } else {
- mFinalIconDrawable = adaptiveIconDrawable;
- }
+ createIconDrawable(adaptiveIconDrawable, mIconSize);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return true;
}
private SplashScreenView fillViewWithIcon(Context context,
int iconSize, Drawable iconDrawable) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
builder.setIconSize(iconSize).setBackgroundColor(mThemeColor)
.setIconBackground(mIconBackground);
@@ -364,6 +359,7 @@
Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
}
splashScreenView.makeSystemUIColorsTransparent();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return splashScreenView;
}
}
@@ -395,7 +391,7 @@
return root < 0.1;
}
- private static SplashScreenView makeSplashscreenContentDrawable(Context ctx,
+ static SplashScreenView makeSplashscreenContent(Context ctx,
int splashscreenContentResId) {
// doesn't support windowSplashscreenContent after S
// TODO add an allowlist to skip some packages if needed
@@ -525,6 +521,7 @@
new TransparentFilterQuantizer();
ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
final Rect initialBounds = drawable.copyBounds();
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
@@ -559,6 +556,7 @@
}
mPalette = builder.generate();
bitmap.recycle();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 8626dbc..a4a83eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -16,11 +16,15 @@
package com.android.wm.shell.startingsurface;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
@@ -28,11 +32,13 @@
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.PathParser;
import android.window.SplashScreenView;
@@ -47,12 +53,57 @@
public class SplashscreenIconDrawableFactory {
static Drawable makeIconDrawable(@ColorInt int backgroundColor,
- @NonNull Drawable foregroundDrawable) {
+ @NonNull Drawable foregroundDrawable, int iconSize) {
if (foregroundDrawable instanceof Animatable) {
return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
+ } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
+ return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
} else {
- // TODO make a light weight drawable instead of AdaptiveIconDrawable
- return new AdaptiveIconDrawable(new ColorDrawable(backgroundColor), foregroundDrawable);
+ return new ImmobileIconDrawable(new AdaptiveIconDrawable(
+ new ColorDrawable(backgroundColor), foregroundDrawable), iconSize);
+ }
+ }
+
+ private static class ImmobileIconDrawable extends Drawable {
+ private Shader mLayersShader;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
+ | Paint.FILTER_BITMAP_FLAG);
+
+ ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) {
+ cachePaint(drawable, iconSize, iconSize);
+ }
+
+ private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
+ final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(layersBitmap);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(canvas);
+ mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP);
+ mPaint.setShader(mLayersShader);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+ canvas.drawRect(bounds, mPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 1;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index b592121..b500813 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -181,11 +181,7 @@
}
int windowFlags = 0;
- final boolean enableHardAccelerated =
- (activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0;
- if (enableHardAccelerated) {
- windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- }
+ windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
final boolean[] showWallpaper = new boolean[1];
final int[] splashscreenContentResId = new int[1];
@@ -246,8 +242,6 @@
params.packageName = activityInfo.packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- params.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
@@ -267,21 +261,31 @@
final int taskId = taskInfo.taskId;
SplashScreenView sView = null;
try {
- sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes,
- splashscreenContentResId[0]);
final View view = win.getDecorView();
final WindowManager wm = mContext.getSystemService(WindowManager.class);
- if (postAddWindow(taskId, appToken, view, wm, params)) {
+ // splash screen content will be deprecated after S.
+ sView = SplashscreenContentDrawer.makeSplashscreenContent(
+ context, splashscreenContentResId[0]);
+ final boolean splashscreenContentCompatible = sView != null;
+ if (splashscreenContentCompatible) {
+ win.setContentView(sView);
+ } else {
+ sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes);
win.setContentView(sView);
sView.cacheRootWindow(win);
}
+ postAddWindow(taskId, appToken, view, wm, params);
} catch (RuntimeException e) {
// don't crash if something else bad happens, for example a
// failure loading resources because we are loading from an app
// on external storage that has been unmounted.
- Slog.w(TAG, " failed creating starting window", e);
+ Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e);
+ sView = null;
} finally {
- setSplashScreenRecord(taskId, sView);
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ record.setSplashScreenView(sView);
+ }
}
}
@@ -328,7 +332,7 @@
ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
}
- protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
+ protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
WindowManager.LayoutParams params) {
boolean shouldSaveView = true;
try {
@@ -349,7 +353,6 @@
removeWindowNoAnimate(taskId);
saveSplashScreenRecord(taskId, view);
}
- return shouldSaveView;
}
private void saveSplashScreenRecord(int taskId, View view) {
@@ -358,13 +361,6 @@
mStartingWindowRecords.put(taskId, tView);
}
- private void setSplashScreenRecord(int taskId, SplashScreenView splashScreenView) {
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- record.setSplashScreenView(splashScreenView);
- }
- }
-
private void removeWindowNoAnimate(int taskId) {
removeWindowSynced(taskId, null, null, false);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 207db9e..8ae0a73 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -83,12 +83,11 @@
}
@Override
- protected boolean postAddWindow(int taskId, IBinder appToken,
+ protected void postAddWindow(int taskId, IBinder appToken,
View view, WindowManager wm, WindowManager.LayoutParams params) {
// listen for addView
mAddWindowForTask = taskId;
mViewThemeResId = view.getContext().getThemeResId();
- return true;
}
@Override
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index b71bb07..1455269 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -120,12 +120,6 @@
int imgHeight = image->height();
sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext());
- if (bitmap->colorType() == kRGBA_F16_SkColorType &&
- !grContext->colorTypeSupportedAsSurface(bitmap->colorType())) {
- ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
- return CopyResult::DestinationInvalid;
- }
-
CopyResult copyResult = CopyResult::UnknownError;
int displayedWidth = imgWidth, displayedHeight = imgHeight;
@@ -159,12 +153,10 @@
bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
SkBitmap* bitmap) {
- /* This intermediate surface is present to work around a bug in SwiftShader that
- * prevents us from reading the contents of the layer's texture directly. The
- * workaround involves first rendering that texture into an intermediate buffer and
- * then reading from the intermediate buffer into the bitmap.
- * Another reason to render in an offscreen buffer is to scale and to avoid an issue b/62262733
- * with reading incorrect data from EGLImage backed SkImage (likely a driver bug).
+ /* This intermediate surface is present to work around limitations that LayerDrawable expects
+ * to render into a GPU backed canvas. Additionally, the offscreen buffer solution works around
+ * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
+ * software buffer.
*/
sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
SkBudgeted::kYes, bitmap->info(), 0,
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/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index 85a083e..ade0ea4 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -55,6 +55,20 @@
}
/**
+ * Returns a CallerIdentity with PID and listener ID information stripped. This is mostly
+ * useful for aggregating information for debug purposes, and should not be used in any API with
+ * security requirements.
+ */
+ public static CallerIdentity forAggregation(CallerIdentity callerIdentity) {
+ if (callerIdentity.getPid() == 0 && callerIdentity.getListenerId() == null) {
+ return callerIdentity;
+ }
+
+ return new CallerIdentity(callerIdentity.getUid(), 0, callerIdentity.getPackageName(),
+ callerIdentity.getAttributionTag(), null);
+ }
+
+ /**
* Creates a CallerIdentity for the current process and context.
*/
public static CallerIdentity fromContext(Context context) {
@@ -180,17 +194,6 @@
}
}
- /**
- * Returns a CallerIdentity corrosponding to this CallerIdentity but with a null listener id.
- */
- public CallerIdentity stripListenerId() {
- if (mListenerId == null) {
- return this;
- } else {
- return new CallerIdentity(mUid, mPid, mPackageName, mAttributionTag, null);
- }
- }
-
@Override
public String toString() {
int length = 10 + mPackageName.length();
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 3cd615b..9774e80 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
/**
* An AudioProfile is specific to an audio format and lists supported sampling rates and
* channel masks for that format. An {@link AudioDeviceInfo} has a list of supported AudioProfiles.
@@ -63,4 +66,29 @@
public @NonNull int[] getSampleRates() {
return mSamplingRates;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{");
+ sb.append(AudioFormat.toLogFriendlyEncoding(mFormat));
+ if (mSamplingRates != null && mSamplingRates.length > 0) {
+ sb.append(", sampling rates=").append(Arrays.toString(mSamplingRates));
+ }
+ if (mChannelMasks != null && mChannelMasks.length > 0) {
+ sb.append(", channel masks=").append(toHexString(mChannelMasks));
+ }
+ if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) {
+ sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks));
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String toHexString(int[] ints) {
+ if (ints == null || ints.length == 0) {
+ return "";
+ }
+ 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/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 3de78bb..644afb7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -99,9 +99,7 @@
/**
- * MediaPlayer class can be used to control playback
- * of audio/video files and streams. An example on how to use the methods in
- * this class can be found in {@link android.widget.VideoView}.
+ * MediaPlayer class can be used to control playback of audio/video files and streams.
*
* <p>MediaPlayer is not thread-safe. Creation of and all access to player instances
* should be on the same thread. If registering <a href="#Callbacks">callbacks</a>,
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/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/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b01878b..85513ca 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -285,6 +285,7 @@
ATrace_endAsyncSection; # introduced=29
ATrace_setCounter; # introduced=29
android_getaddrinfofornetwork; # introduced=23
+ android_getprocnetwork; # introduced=31
android_setprocnetwork; # introduced=23
android_setsocknetwork; # introduced=23
android_res_cancel; # introduced=29
@@ -309,4 +310,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..cc8dd72 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -14,6 +14,8 @@
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
local:
*;
};
diff --git a/native/android/net.c b/native/android/net.c
index a8104fc..d4b8888 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,17 @@
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_getaddrinfofornetwork(net_handle_t network,
const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
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 9ca6d8f..8b99d21 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -45,6 +45,7 @@
public final class NetworkCapabilities implements android.os.Parcelable {
ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long);
method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
+ method public boolean hasUnwantedCapability(int);
field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
@@ -57,7 +58,14 @@
method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
+ public class NetworkRequest implements android.os.Parcelable {
+ method @NonNull public int[] getUnwantedCapabilities();
+ method public boolean hasUnwantedCapability(int);
+ }
+
public static class NetworkRequest.Builder {
+ method @NonNull public android.net.NetworkRequest.Builder addUnwantedCapability(int);
+ method @NonNull public android.net.NetworkRequest.Builder removeUnwantedCapability(int);
method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 703fca4..b19efa3 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -212,10 +212,12 @@
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);
method public void onAutomaticReconnectDisabled();
+ method public void onBandwidthUpdateRequested();
method public void onNetworkUnwanted();
method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
method public void onQosCallbackUnregistered(int);
@@ -233,6 +235,7 @@
method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes);
method public final void sendQosSessionLost(int, int);
method public final void sendSocketKeepaliveEvent(int, int);
+ method @Deprecated public void setLegacySubtype(int, @NonNull String);
method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method public void unregister();
field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
@@ -253,7 +256,12 @@
public static final class NetworkAgentConfig.Builder {
ctor public NetworkAgentConfig.Builder();
method @NonNull public android.net.NetworkAgentConfig build();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
@@ -316,6 +324,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();
@@ -388,6 +409,7 @@
}
public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
field public static final int SUCCESS = 0; // 0x0
}
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index c5b1ff8..c7c0bee 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,
@@ -255,8 +263,8 @@
// 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 },
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index f207830..d490618 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
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 3863ed1..b3d9616 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -362,9 +362,8 @@
public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
- // The subtype can be changed with (TODO) setLegacySubtype, but it starts
- // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description.
- final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, "");
+ final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
+ config.legacyTypeName, config.legacySubTypeName);
ni.setIsAvailable(true);
ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */,
config.getLegacyExtraInfo());
@@ -390,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,
@@ -829,6 +827,7 @@
* @hide
*/
@Deprecated
+ @SystemApi
public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
queueOrSendNetworkInfo(mNetworkInfo);
@@ -962,6 +961,7 @@
* shall try to overwrite this method and produce a bandwidth update if capable.
* @hide
*/
+ @SystemApi
public void onBandwidthUpdateRequested() {
pollLceData();
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
index 5e50a64..fb6fcc1 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
@@ -165,6 +165,12 @@
}
/**
+ * The legacy Sub type of this network agent, or TYPE_NONE if unset.
+ * @hide
+ */
+ public int legacySubType = ConnectivityManager.TYPE_NONE;
+
+ /**
* Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
* Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
*
@@ -190,6 +196,13 @@
}
/**
+ * The name of the legacy Sub network type. It's a free-form string.
+ * @hide
+ */
+ @NonNull
+ public String legacySubTypeName = "";
+
+ /**
* The legacy extra info of the agent. The extra info should only be :
* <ul>
* <li>For cellular agents, the APN name.</li>
@@ -225,6 +238,8 @@
skip464xlat = nac.skip464xlat;
legacyType = nac.legacyType;
legacyTypeName = nac.legacyTypeName;
+ legacySubType = nac.legacySubType;
+ legacySubTypeName = nac.legacySubTypeName;
mLegacyExtraInfo = nac.mLegacyExtraInfo;
}
}
@@ -290,7 +305,6 @@
* and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableNat64Detection() {
@@ -303,7 +317,6 @@
* perform its own carrier-specific provisioning procedure.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableProvisioningNotification() {
@@ -324,6 +337,18 @@
}
/**
+ * Sets the legacy sub-type for this network.
+ *
+ * @param legacySubType the type
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubType(final int legacySubType) {
+ mConfig.legacySubType = legacySubType;
+ return this;
+ }
+
+ /**
* Sets the name of the legacy type of the agent. It's a free-form string used in logging.
* @param legacyTypeName the name
* @return this builder, to facilitate chaining.
@@ -335,10 +360,20 @@
}
/**
+ * Sets the name of the legacy Sub-type of the agent. It's a free-form string.
+ * @param legacySubTypeName the name
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubTypeName(@NonNull String legacySubTypeName) {
+ mConfig.legacySubTypeName = legacySubTypeName;
+ return this;
+ }
+
+ /**
* Sets the legacy extra info of the agent.
* @param legacyExtraInfo the legacy extra info.
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) {
@@ -412,6 +447,8 @@
out.writeInt(skip464xlat ? 1 : 0);
out.writeInt(legacyType);
out.writeString(legacyTypeName);
+ out.writeInt(legacySubType);
+ out.writeString(legacySubTypeName);
out.writeString(mLegacyExtraInfo);
}
@@ -429,6 +466,8 @@
networkAgentConfig.skip464xlat = in.readInt() != 0;
networkAgentConfig.legacyType = in.readInt();
networkAgentConfig.legacyTypeName = in.readString();
+ networkAgentConfig.legacySubType = in.readInt();
+ networkAgentConfig.legacySubTypeName = in.readString();
networkAgentConfig.mLegacyExtraInfo = in.readString();
return networkAgentConfig;
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index c9c0940..4924851 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -538,43 +538,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 =
@@ -639,19 +602,31 @@
}
/**
- * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+ * Removes (if found) the given capability from this {@code NetworkCapability}
+ * instance that were added via addCapability(int) or setCapabilities(int[], int[]).
*
* @param capability the capability to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
- // Note that this method removes capabilities that were added via addCapability(int),
- // addUnwantedCapability(int) or setCapabilities(int[], int[]).
checkValidCapability(capability);
final long mask = ~(1 << capability);
mNetworkCapabilities &= mask;
- mUnwantedNetworkCapabilities &= mask;
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given unwanted capability from this {@code NetworkCapability}
+ * instance that were added via addUnwantedCapability(int) or setCapabilities(int[], int[]).
+ *
+ * @param capability the capability to be removed.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) {
+ checkValidCapability(capability);
+ mUnwantedNetworkCapabilities &= ~(1 << capability);
return this;
}
@@ -723,6 +698,7 @@
}
/** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public boolean hasUnwantedCapability(@NetCapability int capability) {
return isValidCapability(capability)
&& ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
@@ -736,10 +712,16 @@
return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
}
- /** Note this method may result in having the same capability in wanted and unwanted lists. */
private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
- this.mNetworkCapabilities |= nc.mNetworkCapabilities;
- this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities;
+ final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
+ final long unwantedCaps =
+ this.mUnwantedNetworkCapabilities | nc.mUnwantedNetworkCapabilities;
+ if ((wantedCaps & unwantedCaps) != 0) {
+ throw new IllegalArgumentException(
+ "Cannot have the same capability in wanted and unwanted lists.");
+ }
+ this.mNetworkCapabilities = wantedCaps;
+ this.mUnwantedNetworkCapabilities = unwantedCaps;
}
/**
@@ -792,37 +774,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);
}
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index f9b3db1..38691ef 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -313,12 +313,31 @@
*
* @hide
*/
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addUnwantedCapability(capability);
return this;
}
/**
+ * Removes (if found) the given unwanted capability from this builder instance.
+ *
+ * @param capability The unwanted capability to remove.
+ * @return The builder to facilitate chaining.
+ *
+ * @hide
+ */
+ @NonNull
+ @SuppressLint("BuilderSetStyle")
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public Builder removeUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
+ mNetworkCapabilities.removeUnwantedCapability(capability);
+ return this;
+ }
+
+ /**
* Completely clears all the {@code NetworkCapabilities} from this builder instance,
* removing even the capabilities that are set by default when the object is constructed.
*
@@ -575,6 +594,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public boolean hasUnwantedCapability(@NetCapability int capability) {
return networkCapabilities.hasUnwantedCapability(capability);
}
@@ -679,4 +699,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..16ae55f 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}.
diff --git a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
index d007a95..f6cae72 100644
--- a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
+++ b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
@@ -55,36 +55,68 @@
static final String TAG = "SocketKeepalive";
/**
- * No errors.
+ * Success. It indicates there is no error.
* @hide
*/
@SystemApi
public static final int SUCCESS = 0;
- /** @hide */
+ /**
+ * No keepalive. This should only be internally as it indicates There is no keepalive.
+ * It should not propagate to applications.
+ * @hide
+ */
public static final int NO_KEEPALIVE = -1;
- /** @hide */
+ /**
+ * Data received.
+ * @hide
+ */
public static final int DATA_RECEIVED = -2;
- /** @hide */
+ /**
+ * The binder died.
+ * @hide
+ */
public static final int BINDER_DIED = -10;
- /** The specified {@code Network} is not connected. */
+ /**
+ * The invalid network. It indicates the specified {@code Network} is not connected.
+ */
public static final int ERROR_INVALID_NETWORK = -20;
- /** The specified IP addresses are invalid. For example, the specified source IP address is
- * not configured on the specified {@code Network}. */
+
+ /**
+ * The invalid IP addresses. Indicates the specified IP addresses are invalid.
+ * For example, the specified source IP address is not configured on the
+ * specified {@code Network}.
+ */
public static final int ERROR_INVALID_IP_ADDRESS = -21;
- /** The requested port is invalid. */
+
+ /**
+ * The port is invalid.
+ */
public static final int ERROR_INVALID_PORT = -22;
- /** The packet length is invalid (e.g., too long). */
+
+ /**
+ * The length is invalid (e.g. too long).
+ */
public static final int ERROR_INVALID_LENGTH = -23;
- /** The packet transmission interval is invalid (e.g., too short). */
+
+ /**
+ * The interval is invalid (e.g. too short).
+ */
public static final int ERROR_INVALID_INTERVAL = -24;
- /** The target socket is invalid. */
+
+ /**
+ * The socket is invalid.
+ */
public static final int ERROR_INVALID_SOCKET = -25;
- /** The target socket is not idle. */
+
+ /**
+ * The socket is not idle.
+ */
public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
/**
* The stop reason is uninitialized. This should only be internally used as initial state
* of stop reason, instead of propagating to application.
@@ -92,15 +124,29 @@
*/
public static final int ERROR_STOP_REASON_UNINITIALIZED = -27;
- /** The device does not support this request. */
+ /**
+ * The request is unsupported.
+ */
public static final int ERROR_UNSUPPORTED = -30;
- /** @hide TODO: delete when telephony code has been updated. */
- public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
- /** The hardware returned an error. */
+
+ /**
+ * There was a hardware error.
+ */
public static final int ERROR_HARDWARE_ERROR = -31;
- /** The limitation of resource is reached. */
+
+ /**
+ * Resources are insufficient (e.g. all hardware slots are in use).
+ */
public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
+ /**
+ * There was no such slot. This should only be internally as it indicates
+ * a programming error in the system server. It should not propagate to
+ * applications.
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_NO_SUCH_SLOT = -33;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -111,7 +157,8 @@
ERROR_INVALID_LENGTH,
ERROR_INVALID_INTERVAL,
ERROR_INVALID_SOCKET,
- ERROR_SOCKET_NOT_IDLE
+ ERROR_SOCKET_NOT_IDLE,
+ ERROR_NO_SUCH_SLOT
})
public @interface ErrorCode {}
@@ -122,7 +169,6 @@
ERROR_INVALID_LENGTH,
ERROR_UNSUPPORTED,
ERROR_INSUFFICIENT_RESOURCES,
- ERROR_HARDWARE_UNSUPPORTED
})
public @interface KeepaliveEvent {}
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/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 5d4078d..fbb84fd 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -242,7 +242,7 @@
<bool name="def_hdmiControlAutoDeviceOff">true</bool>
<!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED -->
- <bool name="def_swipe_bottom_to_notification_enabled">true</bool>
+ <bool name="def_swipe_bottom_to_notification_enabled">false</bool>
<!-- Default for Settings.Secure.ONE_HANDED_MODE_ENABLED -->
<bool name="def_one_handed_mode_enabled">false</bool>
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 19bcf95..6d44138c 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -48,10 +48,10 @@
<ImageView
android:id="@+id/preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0px"
+ android:layout_height="0px"
android:layout_marginBottom="42dp"
- android:layout_marginHorizontal="48dp"
+ android:paddingHorizontal="48dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/save"
@@ -64,8 +64,8 @@
<com.android.systemui.screenshot.CropView
android:id="@+id/crop_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0px"
+ android:layout_height="0px"
android:layout_marginBottom="42dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 31da4f0..ff4e8e0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -334,7 +334,6 @@
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">6dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
- <dimen name="screenshot_action_container_padding_left">96dp</dimen>
<dimen name="screenshot_action_container_padding_right">8dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index d3168c8..a7c1451 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -14,14 +14,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
import android.metrics.LogMaker;
import android.os.AsyncTask;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
@@ -30,12 +24,6 @@
import android.provider.Settings;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -43,7 +31,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
import com.android.systemui.assist.ui.DefaultUiController;
import com.android.systemui.dagger.SysUISingleton;
@@ -97,8 +84,6 @@
// Note that VERBOSE logging may leak PII (e.g. transcription contents).
private static final boolean VERBOSE = false;
- private static final String ASSIST_ICON_METADATA_NAME =
- "com.android.systemui.action_assist_icon";
private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
public static final String INVOCATION_TYPE_KEY = "invocation_type";
@@ -123,69 +108,29 @@
private static final long TIMEOUT_ACTIVITY = 1000;
protected final Context mContext;
- private final WindowManager mWindowManager;
private final AssistDisclosure mAssistDisclosure;
- private final InterestingConfigChanges mInterestingConfigChanges;
private final PhoneStateMonitor mPhoneStateMonitor;
private final AssistHandleBehaviorController mHandleController;
private final UiController mUiController;
protected final Lazy<SysUiState> mSysUiState;
protected final AssistLogger mAssistLogger;
- private AssistOrbContainer mView;
private final DeviceProvisionedController mDeviceProvisionedController;
private final CommandQueue mCommandQueue;
+ private final AssistOrbController mOrbController;
protected final AssistUtils mAssistUtils;
- private final boolean mShouldEnableOrb;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@Override
public void onFailed() throws RemoteException {
- mView.post(mHideRunnable);
+ mOrbController.postHide();
}
@Override
public void onShown() throws RemoteException {
- mView.post(mHideRunnable);
- }
- };
-
- private Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mView.removeCallbacks(this);
- mView.show(false /* show */, true /* animate */);
- }
- };
-
- private ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
- return;
- }
- boolean visible = false;
- if (mView != null) {
- visible = mView.isShowing();
- if (mView.isAttachedToWindow()) {
- mWindowManager.removeView(mView);
- }
- }
-
- mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
- R.layout.assist_orb, null);
- mView.setVisibility(View.GONE);
- mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
- WindowManager.LayoutParams lp = getLayoutParams();
- mWindowManager.addView(mView, lp);
- if (visible) {
- mView.show(true /* show */, false /* animate */);
- }
+ mOrbController.postHide();
}
};
@@ -205,21 +150,15 @@
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mAssistUtils = assistUtils;
mAssistDisclosure = new AssistDisclosure(context, new Handler());
mPhoneStateMonitor = phoneStateMonitor;
mHandleController = handleController;
mAssistLogger = assistLogger;
- configurationController.addCallback(mConfigurationListener);
+ mOrbController = new AssistOrbController(configurationController, context);
registerVoiceInteractionSessionListener();
- mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
- | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
- | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
- mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
- mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
mUiController = defaultUiController;
@@ -282,7 +221,7 @@
}
protected boolean shouldShowOrb() {
- return false;
+ return !ActivityManager.isLowRamDeviceStatic();
}
public void startAssist(Bundle args) {
@@ -293,10 +232,8 @@
final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) {
- showOrb(assistComponent, isService);
- mView.postDelayed(mHideRunnable, isService
- ? TIMEOUT_SERVICE
- : TIMEOUT_ACTIVITY);
+ mOrbController.showOrb(assistComponent, isService);
+ mOrbController.postHideDelayed(isService ? TIMEOUT_SERVICE : TIMEOUT_ACTIVITY);
}
if (args == null) {
@@ -340,30 +277,6 @@
mAssistUtils.hideCurrentSession();
}
- private WindowManager.LayoutParams getLayoutParams() {
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
- WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- lp.token = new Binder();
- lp.gravity = Gravity.BOTTOM | Gravity.START;
- lp.setTitle("AssistPreviewPanel");
- lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
- return lp;
- }
-
- private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
- maybeSwapSearchIcon(assistComponent, isService);
- if (mShouldEnableOrb) {
- mView.show(true /* show */, true /* animate */);
- }
- }
-
private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
boolean isService) {
if (isService) {
@@ -440,44 +353,6 @@
return mAssistUtils.isSessionRunning();
}
- private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
- replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
- isService);
- }
-
- public void replaceDrawable(ImageView v, ComponentName component, String name,
- boolean isService) {
- if (component != null) {
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = isService
- ? packageManager.getServiceInfo(
- component, PackageManager.GET_META_DATA).metaData
- : packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForApplication(
- component.getPackageName());
- v.setImageDrawable(res.getDrawable(iconResId));
- return;
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- if (VERBOSE) {
- Log.v(TAG, "Assistant component "
- + component.flattenToShortString() + " not found");
- }
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- v.setImageDrawable(null);
- }
-
protected AssistHandleBehaviorController getHandleBehaviorController() {
return mHandleController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
index f78436a..fe48c26 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
@@ -55,14 +55,17 @@
mOrb = (AssistOrbView) findViewById(R.id.assist_orb);
}
- public void show(final boolean show, boolean animate) {
+ public void show(final boolean show, boolean animate, Runnable onDone) {
if (show) {
if (getVisibility() != View.VISIBLE) {
setVisibility(View.VISIBLE);
if (animate) {
- startEnterAnimation();
+ startEnterAnimation(onDone);
} else {
reset();
+ if (onDone != null) {
+ onDone.run();
+ }
}
}
} else {
@@ -72,10 +75,16 @@
public void run() {
mAnimatingOut = false;
setVisibility(View.GONE);
+ if (onDone != null) {
+ onDone.run();
+ }
}
});
} else {
setVisibility(View.GONE);
+ if (onDone != null) {
+ onDone.run();
+ }
}
}
}
@@ -87,7 +96,7 @@
mNavbarScrim.setAlpha(1f);
}
- private void startEnterAnimation() {
+ private void startEnterAnimation(Runnable onDone) {
if (mAnimatingOut) {
return;
}
@@ -106,7 +115,8 @@
.alpha(1f)
.setDuration(300)
.setStartDelay(0)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .withEndAction(onDone);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
new file mode 100644
index 0000000..81a13a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -0,0 +1,180 @@
+/*
+ * 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.assist;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+/**
+ * AssistOrbController controls the showing and hiding of the assistant orb.
+ */
+public class AssistOrbController {
+ private static final String ASSIST_ICON_METADATA_NAME =
+ "com.android.systemui.action_assist_icon";
+ private static final String TAG = "AssistOrbController";
+ private static final boolean VERBOSE = false;
+
+ private final InterestingConfigChanges mInterestingConfigChanges;
+ private AssistOrbContainer mView;
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+
+ private Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mView.removeCallbacks(this);
+ mView.show(false /* show */, true /* animate */, () -> {
+ mWindowManager.removeView(mView);
+ });
+ }
+ };
+
+ private ConfigurationController.ConfigurationListener mConfigurationListener =
+ new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
+ return;
+ }
+ boolean visible = false;
+ if (mView != null) {
+ visible = mView.isShowing();
+ if (mView.isAttachedToWindow()) {
+ mWindowManager.removeView(mView);
+ }
+ }
+
+ if (visible) {
+ showOrb(false);
+ }
+ }
+ };
+
+ AssistOrbController(ConfigurationController configurationController, Context context) {
+ mContext = context;
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
+
+ configurationController.addCallback(mConfigurationListener);
+ mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
+ }
+
+ public void postHide() {
+ mView.post(mHideRunnable);
+ }
+
+ public void postHideDelayed(long delayMs) {
+ mView.postDelayed(mHideRunnable, delayMs);
+ }
+
+ private void showOrb(boolean animated) {
+ if (mView == null) {
+ mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
+ R.layout.assist_orb, null);
+ mView.setVisibility(View.GONE);
+ mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ }
+ if (!mView.isAttachedToWindow()) {
+ WindowManager.LayoutParams params = getLayoutParams();
+ mWindowManager.addView(mView, params);
+ }
+ mView.show(true, animated, null);
+ }
+
+ private WindowManager.LayoutParams getLayoutParams() {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.token = new Binder();
+ lp.gravity = Gravity.BOTTOM | Gravity.START;
+ lp.setTitle("AssistPreviewPanel");
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+ return lp;
+ }
+
+ public void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
+ showOrb(true);
+ maybeSwapSearchIcon(assistComponent, isService);
+ }
+
+ private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
+ replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
+ isService);
+ }
+
+ public void replaceDrawable(ImageView v, ComponentName component, String name,
+ boolean isService) {
+ if (component != null) {
+ try {
+ PackageManager packageManager = mContext.getPackageManager();
+ // Look for the search icon specified in the activity meta-data
+ Bundle metaData = isService
+ ? packageManager.getServiceInfo(
+ component, PackageManager.GET_META_DATA).metaData
+ : packageManager.getActivityInfo(
+ component, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(name);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForApplication(
+ component.getPackageName());
+ v.setImageDrawable(res.getDrawable(iconResId));
+ return;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (VERBOSE) {
+ Log.v(TAG, "Assistant component "
+ + component.flattenToShortString() + " not found");
+ }
+ } catch (Resources.NotFoundException nfe) {
+ Log.w(TAG, "Failed to swap drawable from "
+ + component.flattenToShortString(), nfe);
+ }
+ }
+ v.setImageDrawable(null);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index eedcdab..b1689f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -48,7 +48,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
@@ -86,7 +85,6 @@
private final View mRingerContainer;
private final QSTileHost mQSTileHost;
private final StatusBarIconController mStatusBarIconController;
- private final CommandQueue mCommandQueue;
private final DemoModeController mDemoModeController;
private final UserTracker mUserTracker;
private final StatusIconContainer mIconContainer;
@@ -204,7 +202,7 @@
PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
ActivityStarter activityStarter, UiEventLogger uiEventLogger,
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
- CommandQueue commandQueue, DemoModeController demoModeController,
+ DemoModeController demoModeController,
UserTracker userTracker, QuickQSPanelController quickQSPanelController,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
PrivacyLogger privacyLogger,
@@ -219,7 +217,6 @@
mUiEventLogger = uiEventLogger;
mQSTileHost = qsTileHost;
mStatusBarIconController = statusBarIconController;
- mCommandQueue = commandQueue;
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
@@ -238,7 +235,7 @@
mRingerContainer = mView.findViewById(R.id.ringer_container);
mIconContainer = mView.findViewById(R.id.statusIcons);
- mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, mCommandQueue);
+ mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
mColorExtractor = colorExtractor;
mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 11c7c9a..01afacf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -409,7 +409,9 @@
}
Rect bounds = drawable.getBounds();
float imageRatio = bounds.width() / (float) bounds.height();
- float viewRatio = mPreview.getWidth() / (float) mPreview.getHeight();
+ int previewWidth = mPreview.getWidth() - mPreview.getPaddingLeft()
+ - mPreview.getPaddingRight();
+ float viewRatio = previewWidth / (float) mPreview.getHeight();
if (imageRatio > viewRatio) {
// Image is full width and height is constrained, compute extra padding to inform
@@ -417,7 +419,7 @@
float imageHeight = mPreview.getHeight() * viewRatio / imageRatio;
int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2;
mCropView.setExtraPadding(extraPadding, extraPadding);
- mCropView.setImageWidth(mPreview.getWidth());
+ mCropView.setImageWidth(previewWidth);
} else {
// Image is full height
mCropView.setExtraPadding(0, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 5c650ad..d15f1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -142,6 +142,8 @@
private ScreenshotViewCallback mCallbacks;
private Animator mDismissAnimation;
private boolean mPendingSharedTransition;
+ private GestureDetector mSwipeDetector;
+ private SwipeDismissHandler mSwipeDismissHandler;
private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
@@ -181,6 +183,23 @@
mContext.getDisplay().getRealMetrics(mDisplayMetrics);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+
+ mSwipeDetector = new GestureDetector(mContext,
+ new GestureDetector.SimpleOnGestureListener() {
+ final Rect mActionsRect = new Rect();
+
+ @Override
+ public boolean onScroll(
+ MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+ mActionsContainer.getBoundsOnScreen(mActionsRect);
+ // return true if we aren't in the actions bar, or if we are but it isn't
+ // scrollable in the direction of movement
+ return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
+ || !mActionsContainer.canScrollHorizontally((int) distanceX);
+ }
+ });
+ mSwipeDetector.setIsLongpressEnabled(false);
+ mSwipeDismissHandler = new SwipeDismissHandler();
}
/**
@@ -230,6 +249,15 @@
inoutInfo.touchableRegion.set(touchRegion);
}
+ @Override // ViewGroup
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // always pass through the down event so the swipe handler knows the initial state
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mSwipeDismissHandler.onTouch(this, ev);
+ }
+ return mSwipeDetector.onTouchEvent(ev);
+ }
+
@Override // View
protected void onFinishInflate() {
mScreenshotStatic = requireNonNull(findViewById(R.id.global_screenshot_static));
@@ -455,11 +483,7 @@
createScreenshotActionsShadeAnimation().start();
- SwipeDismissHandler swipeDismissHandler = new SwipeDismissHandler();
- mScreenshotPreview.setOnTouchListener(swipeDismissHandler);
- mActionsContainer.setOnTouchListener(swipeDismissHandler);
- mActionsContainerBackground.setOnTouchListener(swipeDismissHandler);
- mBackgroundProtection.setOnTouchListener(swipeDismissHandler);
+ setOnTouchListener(mSwipeDismissHandler);
}
});
@@ -776,30 +800,16 @@
}
class SwipeDismissHandler implements OnTouchListener {
-
- // if distance moved on ACTION_UP is less than this, register a click
- // otherwise, run return animator
- private static final float CLICK_MOVEMENT_THRESHOLD_DP = 1;
// distance needed to register a dismissal
private static final float DISMISS_DISTANCE_THRESHOLD_DP = 30;
private final GestureDetector mGestureDetector;
- private final float mDismissStartX;
- private final Rect mActionsRect = new Rect();
private float mStartX;
- private float mStartY;
- private float mTranslationX = 0;
-
- // tracks whether mStartX has been set for this interaction
- private boolean mInteractionStarted = false;
- // tracks whether we're dragging the UI (as opposed to scrolling the actions bar)
- private boolean mIsDragging = false;
SwipeDismissHandler() {
GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
mGestureDetector = new GestureDetector(mContext, gestureListener);
- mDismissStartX = mDismissButton.getX();
}
@Override
@@ -807,13 +817,9 @@
boolean gestureResult = mGestureDetector.onTouchEvent(event);
mCallbacks.onUserInteraction();
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mInteractionStarted = true;
mStartX = event.getRawX();
- mStartY = event.getRawY();
return true;
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- mInteractionStarted = false;
- mIsDragging = false;
if (isPastDismissThreshold()
&& (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
if (DEBUG_INPUT) {
@@ -821,87 +827,47 @@
}
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED);
animateDismissal(createSwipeDismissAnimation());
- return true;
- } else if (MathUtils.dist(mStartX, mStartY, event.getRawX(), event.getRawY())
- > dpToPx(CLICK_MOVEMENT_THRESHOLD_DP)) {
- // if we've moved a non-negligible distance (but not past the threshold),
+ } else {
+ // if we've moved, but not past the threshold, start the return animation
if (DEBUG_DISMISS) {
Log.d(TAG, "swipe gesture abandoned");
}
- // start the return animation
if ((mDismissAnimation == null || !mDismissAnimation.isRunning())) {
createSwipeReturnAnimation().start();
}
- return true;
- } else {
- if (view == mScreenshotPreview) {
- mScreenshotPreview.performClick();
- }
}
+ return true;
}
return gestureResult;
}
class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
-
- /**
- * This is somewhat complicated to handle because we want to allow scrolling the actions
- * bar (if it extends off the screen) as well as dismissing the UI horizontally by
- * dragging the actions bar. In addition, we don't get the initial ACTION_DOWN because
- * it is consumed by the action bar view.
- *
- * So, we use a gated system: first, keep track of the pointer location as we move;
- * next, check whether the actions bar can scroll in the direction we're moving in. If
- * it can, let the actions bar handle the event; otherwise, we've gone as far as we can
- * and can start dragging the UI instead.
- */
@Override
public boolean onScroll(
MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
- mActionsContainer.getBoundsOnScreen(mActionsRect);
- if (!mInteractionStarted) {
- if (mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())) {
- mStartX = ev2.getRawX();
- mInteractionStarted = true;
- }
- } else {
- float distance = ev2.getRawX() - mStartX;
- if ((mIsDragging && distance * mTranslationX > 0)
- || !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
- || !mActionsContainer.canScrollHorizontally(-1 * (int) distance)) {
- mIsDragging = true;
- mTranslationX = distance;
- mScreenshotStatic.setTranslationX(mTranslationX);
- return true;
- } else {
- mStartX = ev2.getRawX();
- }
- }
- return false;
+ mScreenshotStatic.setTranslationX(ev2.getRawX() - mStartX);
+ return true;
}
}
private boolean isPastDismissThreshold() {
- if (mDirectionLTR) {
- return mTranslationX <= -1 * dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
- } else {
- return mTranslationX >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
- }
+ float distance = Math.abs(mScreenshotStatic.getTranslationX());
+ return distance >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
}
private ValueAnimator createSwipeDismissAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mTranslationX;
- float finalX = mDirectionLTR
- ? -1 * (mActionsContainerBackground.getX()
- + mActionsContainerBackground.getWidth())
+ float startX = mScreenshotStatic.getTranslationX();
+ // make sure the UI gets all the way off the screen in the direction of movement
+ // (the actions container background is guaranteed to be both the leftmost and
+ // rightmost UI element in LTR and RTL)
+ float finalX = startX < 0
+ ? -1 * mActionsContainerBackground.getRight()
: mDisplayMetrics.widthPixels;
anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(startX, finalX,
- animation.getAnimatedFraction());
+ float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
mScreenshotStatic.setTranslationX(translation);
-
setAlpha(1 - animation.getAnimatedFraction());
});
anim.setDuration(400);
@@ -910,9 +876,8 @@
private ValueAnimator createSwipeReturnAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mTranslationX;
+ float startX = mScreenshotStatic.getTranslationX();
float finalX = 0;
- mTranslationX = 0;
anim.addUpdateListener(animation -> {
float translation = MathUtils.lerp(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index ca3923f..c565a271 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -38,8 +38,6 @@
import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.Properties;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
@@ -48,7 +46,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
@@ -147,23 +144,6 @@
private ImageView mBackdropFront;
private ImageView mBackdropBack;
- private boolean mShowCompactMediaSeekbar;
- private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(Properties properties) {
- for (String name : properties.getKeyset()) {
- if (SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED.equals(name)) {
- String value = properties.getString(name, null);
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: compact media seekbar flag updated: " + value);
- }
- mShowCompactMediaSeekbar = "true".equals(value);
- }
- }
- }
- };
-
private final MediaController.Callback mMediaListener = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -231,14 +211,6 @@
setupNotifPipeline();
mUsingNotifPipeline = true;
}
-
- mShowCompactMediaSeekbar = "true".equals(
- DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
-
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mContext.getMainExecutor(),
- mPropertiesChangedListener);
}
private void setupNotifPipeline() {
@@ -405,10 +377,6 @@
return mMediaMetadata;
}
- public boolean getShowCompactMediaSeekbar() {
- return mShowCompactMediaSeekbar;
- }
-
public Icon getMediaIcon() {
if (mMediaNotificationKey == null) {
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
deleted file mode 100644
index f5a76f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-
-/**
- * A utility class to colorize bitmaps with a color gradient and a special blending mode
- */
-public class ImageGradientColorizer {
- public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
- int width = drawable.getIntrinsicWidth();
- int height = drawable.getIntrinsicHeight();
- int size = Math.min(width, height);
- int widthInset = (width - size) / 2;
- int heightInset = (height - size) / 2;
- drawable = drawable.mutate();
- drawable.setBounds(- widthInset, - heightInset, width - widthInset, height - heightInset);
- Bitmap newBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(newBitmap);
-
- // Values to calculate the luminance of a color
- float lr = 0.2126f;
- float lg = 0.7152f;
- float lb = 0.0722f;
-
- // Extract the red, green, blue components of the color extraction color in
- // float and int form
- int tri = Color.red(backgroundColor);
- int tgi = Color.green(backgroundColor);
- int tbi = Color.blue(backgroundColor);
-
- float tr = tri / 255f;
- float tg = tgi / 255f;
- float tb = tbi / 255f;
-
- // Calculate the luminance of the color extraction color
- float cLum = (tr * lr + tg * lg + tb * lb) * 255;
-
- ColorMatrix m = new ColorMatrix(new float[] {
- lr, lg, lb, 0, tri - cLum,
- lr, lg, lb, 0, tgi - cLum,
- lr, lg, lb, 0, tbi - cLum,
- 0, 0, 0, 1, 0,
- });
-
- Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- LinearGradient linearGradient = new LinearGradient(0, 0, size, 0,
- new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
- new float[] {0.0f, 0.4f, 1.0f}, Shader.TileMode.CLAMP);
- paint.setShader(linearGradient);
- Bitmap fadeIn = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas fadeInCanvas = new Canvas(fadeIn);
- drawable.clearColorFilter();
- drawable.draw(fadeInCanvas);
-
- if (isRtl) {
- // Let's flip the gradient
- fadeInCanvas.translate(size, 0);
- fadeInCanvas.scale(-1, 1);
- }
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
- fadeInCanvas.drawPaint(paint);
-
- Paint coloredPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- coloredPaint.setColorFilter(new ColorMatrixColorFilter(m));
- coloredPaint.setAlpha((int) (0.5f * 255));
- canvas.drawBitmap(fadeIn, 0, 0, coloredPaint);
-
- linearGradient = new LinearGradient(0, 0, size, 0,
- new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
- new float[] {0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP);
- paint.setShader(linearGradient);
- fadeInCanvas.drawPaint(paint);
- canvas.drawBitmap(fadeIn, 0, 0, null);
-
- return newBitmap;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
index 2586e94..732c115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
@@ -16,237 +16,30 @@
package com.android.systemui.statusbar.notification;
-import android.app.Notification;
-import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.util.LayoutDirection;
-import androidx.annotation.VisibleForTesting;
import androidx.palette.graphics.Palette;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.settingslib.Utils;
-
import java.util.List;
/**
- * A class the processes media notifications and extracts the right text and background colors.
+ * A gutted class that now contains only a color extraction utility used by the
+ * MediaArtworkProcessor, which has otherwise supplanted this.
+ *
+ * TODO(b/182926117): move this into MediaArtworkProcessor.kt
*/
public class MediaNotificationProcessor {
/**
- * The fraction below which we select the vibrant instead of the light/dark vibrant color
- */
- private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f;
-
- /**
- * Minimum saturation that a muted color must have if there exists if deciding between two
- * colors
- */
- private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f;
-
- /**
- * Minimum fraction that any color must have to be picked up as a text color
- */
- private static final double MINIMUM_IMAGE_FRACTION = 0.002;
-
- /**
- * The population fraction to select the dominant color as the text color over a the colored
- * ones.
- */
- private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f;
-
- /**
* The population fraction to select a white or black color as the background over a color.
*/
private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.90f;
private static final int RESIZE_BITMAP_AREA = 150 * 150;
- private final ImageGradientColorizer mColorizer;
- private final Context mContext;
- private final Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl);
- /**
- * The context of the notification. This is the app context of the package posting the
- * notification.
- */
- private final Context mPackageContext;
-
- public MediaNotificationProcessor(Context context, Context packageContext) {
- this(context, packageContext, new ImageGradientColorizer());
- }
-
- @VisibleForTesting
- MediaNotificationProcessor(Context context, Context packageContext,
- ImageGradientColorizer colorizer) {
- mContext = context;
- mPackageContext = packageContext;
- mColorizer = colorizer;
- }
-
- /**
- * Processes a builder of a media notification and calculates the appropriate colors that should
- * be used.
- *
- * @param notification the notification that is being processed
- * @param builder the recovered builder for the notification. this will be modified
- */
- public void processNotification(Notification notification, Notification.Builder builder) {
- Icon largeIcon = notification.getLargeIcon();
- Bitmap bitmap = null;
- Drawable drawable = null;
- if (largeIcon != null) {
- // We're transforming the builder, let's make sure all baked in RemoteViews are
- // rebuilt!
- builder.setRebuildStyledRemoteViews(true);
- drawable = largeIcon.loadDrawable(mPackageContext);
- int backgroundColor = 0;
- if (notification.isColorizedMedia()) {
- int width = drawable.getIntrinsicWidth();
- int height = drawable.getIntrinsicHeight();
- int area = width * height;
- if (area > RESIZE_BITMAP_AREA) {
- double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area);
- width = (int) (factor * width);
- height = (int) (factor * height);
- }
- bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, width, height);
- drawable.draw(canvas);
-
- Palette.Builder paletteBuilder = generateArtworkPaletteBuilder(bitmap);
- Palette palette = paletteBuilder.generate();
- Palette.Swatch backgroundSwatch = findBackgroundSwatch(palette);
- backgroundColor = backgroundSwatch.getRgb();
- // we want most of the full region again, slightly shifted to the right
- float textColorStartWidthFraction = 0.4f;
- paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0,
- bitmap.getWidth(),
- bitmap.getHeight());
- // We're not filtering on white or black
- if (!isWhiteOrBlack(backgroundSwatch.getHsl())) {
- final float backgroundHue = backgroundSwatch.getHsl()[0];
- paletteBuilder.addFilter((rgb, hsl) -> {
- // at least 10 degrees hue difference
- float diff = Math.abs(hsl[0] - backgroundHue);
- return diff > 10 && diff < 350;
- });
- }
- paletteBuilder.addFilter(mBlackWhiteFilter);
- palette = paletteBuilder.generate();
- int foregroundColor = selectForegroundColor(backgroundColor, palette);
- builder.setColorPalette(backgroundColor, foregroundColor);
- } else {
- backgroundColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground)
- .getDefaultColor();
- }
- Bitmap colorized = mColorizer.colorize(drawable, backgroundColor,
- mContext.getResources().getConfiguration().getLayoutDirection() ==
- LayoutDirection.RTL);
- builder.setLargeIcon(Icon.createWithBitmap(colorized));
- }
- }
-
- /**
- * Select a foreground color depending on whether the background color is dark or light
- * @param backgroundColor Background color to coordinate with
- * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
- * @return foreground color
- */
- public static int selectForegroundColor(int backgroundColor, Palette palette) {
- if (ContrastColorUtil.isColorLight(backgroundColor)) {
- return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(),
- palette.getVibrantSwatch(),
- palette.getDarkMutedSwatch(),
- palette.getMutedSwatch(),
- palette.getDominantSwatch(),
- Color.BLACK);
- } else {
- return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(),
- palette.getVibrantSwatch(),
- palette.getLightMutedSwatch(),
- palette.getMutedSwatch(),
- palette.getDominantSwatch(),
- Color.WHITE);
- }
- }
-
- private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
- Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch,
- Palette.Swatch dominantSwatch, int fallbackColor) {
- Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
- if (coloredCandidate == null) {
- coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch);
- }
- if (coloredCandidate != null) {
- if (dominantSwatch == coloredCandidate) {
- return coloredCandidate.getRgb();
- } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation()
- < POPULATION_FRACTION_FOR_DOMINANT
- && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) {
- return dominantSwatch.getRgb();
- } else {
- return coloredCandidate.getRgb();
- }
- } else if (hasEnoughPopulation(dominantSwatch)) {
- return dominantSwatch.getRgb();
- } else {
- return fallbackColor;
- }
- }
-
- private static Palette.Swatch selectMutedCandidate(Palette.Swatch first,
- Palette.Swatch second) {
- boolean firstValid = hasEnoughPopulation(first);
- boolean secondValid = hasEnoughPopulation(second);
- if (firstValid && secondValid) {
- float firstSaturation = first.getHsl()[1];
- float secondSaturation = second.getHsl()[1];
- float populationFraction = first.getPopulation() / (float) second.getPopulation();
- if (firstSaturation * populationFraction > secondSaturation) {
- return first;
- } else {
- return second;
- }
- } else if (firstValid) {
- return first;
- } else if (secondValid) {
- return second;
- }
- return null;
- }
-
- private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first,
- Palette.Swatch second) {
- boolean firstValid = hasEnoughPopulation(first);
- boolean secondValid = hasEnoughPopulation(second);
- if (firstValid && secondValid) {
- int firstPopulation = first.getPopulation();
- int secondPopulation = second.getPopulation();
- if (firstPopulation / (float) secondPopulation
- < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
- return second;
- } else {
- return first;
- }
- } else if (firstValid) {
- return first;
- } else if (secondValid) {
- return second;
- }
- return null;
- }
-
- private static boolean hasEnoughPopulation(Palette.Swatch swatch) {
- // We want a fraction that is at least 1% of the image
- return swatch != null
- && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
+ private MediaNotificationProcessor() {
}
/**
@@ -279,7 +72,7 @@
List<Palette.Swatch> swatches = palette.getSwatches();
float highestNonWhitePopulation = -1;
Palette.Swatch second = null;
- for (Palette.Swatch swatch: swatches) {
+ for (Palette.Swatch swatch : swatches) {
if (swatch != dominantSwatch
&& swatch.getPopulation() > highestNonWhitePopulation
&& !isWhiteOrBlack(swatch.getHsl())) {
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 6cf5c30..815cfb3 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
@@ -668,7 +668,6 @@
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
boolean isMessagingLayout = contractedView instanceof MessagingLayout;
boolean isCallLayout = contractedView instanceof CallLayout;
- boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (customView && beforeS && !mIsSummaryWithChildren) {
if (beforeN) {
@@ -678,12 +677,6 @@
} else {
smallHeight = mMaxSmallHeightBeforeS;
}
- } else if (isMediaLayout) {
- // TODO(b/172652345): MediaStyle notifications currently look broken when we enforce
- // the standard notification height, so we have to afford them more vertical space to
- // make sure we don't crop them terribly. We actually need to revisit this and give
- // them a headerless design, then remove this hack.
- smallHeight = showCompactMediaSeekbar ? mMaxSmallHeightMedia : mMaxSmallHeightBeforeS;
} else if (isMessagingLayout) {
// TODO(b/173204301): MessagingStyle notifications currently look broken when we enforce
// the standard notification height, so we have to afford them more vertical space to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 58b87cd..73c4b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -40,13 +40,11 @@
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaDataManagerKt;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -799,13 +797,6 @@
// For all of our templates, we want it to be RTL
packageContext = new RtlEnabledContext(packageContext);
}
- Notification notification = sbn.getNotification();
- if (notification.isMediaNotification() && !(mIsMediaInQS
- && MediaDataManagerKt.isMediaNotification(sbn))) {
- MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
- packageContext);
- processor.processNotification(notification, recoveredBuilder);
- }
if (mEntry.getRanking().isConversation()) {
mConversationProcessor.processNotification(mEntry, recoveredBuilder);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 2535e5d..c75cd78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -16,341 +16,26 @@
package com.android.systemui.statusbar.notification.row.wrapper;
-import static com.android.systemui.Dependency.MAIN_HANDLER;
-
-import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewStub;
-import android.widget.SeekBar;
-import android.widget.TextView;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.widget.MediaNotificationView;
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import java.util.Timer;
-import java.util.TimerTask;
-
/**
* Wraps a notification containing a media template
*/
public class NotificationMediaTemplateViewWrapper extends NotificationTemplateViewWrapper {
- private static final long PROGRESS_UPDATE_INTERVAL = 1000; // 1s
- private static final String COMPACT_MEDIA_TAG = "media";
- private final Handler mHandler = Dependency.get(MAIN_HANDLER);
- private Timer mSeekBarTimer;
private View mActions;
- private SeekBar mSeekBar;
- private TextView mSeekBarElapsedTime;
- private TextView mSeekBarTotalTime;
- private long mDuration = 0;
- private MediaController mMediaController;
- private MediaMetadata mMediaMetadata;
- private NotificationMediaManager mMediaManager;
- private View mSeekBarView;
- private Context mContext;
- private MetricsLogger mMetricsLogger;
- private boolean mIsViewVisible;
-
- @VisibleForTesting
- protected SeekBar.OnSeekBarChangeListener mSeekListener =
- new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (mMediaController != null) {
- mMediaController.getTransportControls().seekTo(mSeekBar.getProgress());
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_UPDATE));
- }
- }
- };
-
- private MediaNotificationView.VisibilityChangeListener mVisibilityListener =
- new MediaNotificationView.VisibilityChangeListener() {
- @Override
- public void onAggregatedVisibilityChanged(boolean isVisible) {
- mIsViewVisible = isVisible;
- if (isVisible && mMediaController != null) {
- // Restart timer if we're currently playing and didn't already have one going
- PlaybackState state = mMediaController.getPlaybackState();
- if (state != null && state.getState() == PlaybackState.STATE_PLAYING
- && mSeekBarTimer == null && mSeekBarView != null
- && mSeekBarView.getVisibility() != View.GONE) {
- startTimer();
- }
- } else {
- clearTimer();
- }
- }
- };
-
- private View.OnAttachStateChangeListener mAttachStateListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mIsViewVisible = false;
- }
- };
-
- private MediaController.Callback mMediaCallback = new MediaController.Callback() {
- @Override
- public void onSessionDestroyed() {
- clearTimer();
- mMediaController.unregisterCallback(this);
- if (mView instanceof MediaNotificationView) {
- ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
- mView.removeOnAttachStateChangeListener(mAttachStateListener);
- }
- }
-
- @Override
- public void onPlaybackStateChanged(@Nullable PlaybackState state) {
- if (state == null) {
- return;
- }
-
- if (state.getState() != PlaybackState.STATE_PLAYING) {
- // Update the UI once, in case playback info changed while we were paused
- updatePlaybackUi(state);
- clearTimer();
- } else if (mSeekBarTimer == null && mSeekBarView != null
- && mSeekBarView.getVisibility() != View.GONE) {
- startTimer();
- }
- }
-
- @Override
- public void onMetadataChanged(@Nullable MediaMetadata metadata) {
- if (mMediaMetadata == null || !mMediaMetadata.equals(metadata)) {
- mMediaMetadata = metadata;
- updateDuration();
- }
- }
- };
protected NotificationMediaTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
- mContext = ctx;
- mMediaManager = Dependency.get(NotificationMediaManager.class);
- mMetricsLogger = Dependency.get(MetricsLogger.class);
}
private void resolveViews() {
mActions = mView.findViewById(com.android.internal.R.id.media_actions);
- mIsViewVisible = mView.isShown();
-
- final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
- .getParcelable(Notification.EXTRA_MEDIA_SESSION);
-
- boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
- if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
- if (mSeekBarView != null) {
- mSeekBarView.setVisibility(View.GONE);
- }
- return;
- }
-
- // Check for existing media controller and clean up / create as necessary
- boolean shouldUpdateListeners = false;
- if (mMediaController == null || !mMediaController.getSessionToken().equals(token)) {
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaCallback);
- }
- mMediaController = new MediaController(mContext, token);
- shouldUpdateListeners = true;
- }
-
- mMediaMetadata = mMediaController.getMetadata();
- if (mMediaMetadata != null) {
- long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (duration <= 0) {
- // Don't include the seekbar if this is a livestream
- if (mSeekBarView != null && mSeekBarView.getVisibility() != View.GONE) {
- mSeekBarView.setVisibility(View.GONE);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
- clearTimer();
- } else if (mSeekBarView == null && shouldUpdateListeners) {
- // Only log if the controller changed, otherwise we would log multiple times for
- // the same notification when user pauses/resumes
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
- }
- return;
- } else if (mSeekBarView != null && mSeekBarView.getVisibility() == View.GONE) {
- // Otherwise, make sure the seekbar is visible
- mSeekBarView.setVisibility(View.VISIBLE);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
- updateDuration();
- startTimer();
- }
- }
-
- // Inflate the seekbar template
- ViewStub stub = mView.findViewById(R.id.notification_media_seekbar_container);
- if (stub instanceof ViewStub) {
- LayoutInflater layoutInflater = LayoutInflater.from(stub.getContext());
- stub.setLayoutInflater(layoutInflater);
- stub.setLayoutResource(R.layout.notification_material_media_seekbar);
- mSeekBarView = stub.inflate();
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
-
- mSeekBar = mSeekBarView.findViewById(R.id.notification_media_progress_bar);
- mSeekBar.setOnSeekBarChangeListener(mSeekListener);
-
- mSeekBarElapsedTime = mSeekBarView.findViewById(R.id.notification_media_elapsed_time);
- mSeekBarTotalTime = mSeekBarView.findViewById(R.id.notification_media_total_time);
-
- shouldUpdateListeners = true;
- }
-
- if (shouldUpdateListeners) {
- if (mView instanceof MediaNotificationView) {
- MediaNotificationView mediaView = (MediaNotificationView) mView;
- mediaView.addVisibilityListener(mVisibilityListener);
- mView.addOnAttachStateChangeListener(mAttachStateListener);
- }
-
- if (mSeekBarTimer == null) {
- if (mMediaController != null && canSeekMedia(mMediaController.getPlaybackState())) {
- // Log initial state, since it will not be updated
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, 1));
- } else {
- setScrubberVisible(false);
- }
- updateDuration();
- startTimer();
- mMediaController.registerCallback(mMediaCallback);
- }
- }
- updateSeekBarTint(mSeekBarView);
- }
-
- private void startTimer() {
- clearTimer();
- if (mIsViewVisible) {
- mSeekBarTimer = new Timer(true /* isDaemon */);
- mSeekBarTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- mHandler.post(mOnUpdateTimerTick);
- }
- }, 0, PROGRESS_UPDATE_INTERVAL);
- }
- }
-
- private void clearTimer() {
- if (mSeekBarTimer != null) {
- mSeekBarTimer.cancel();
- mSeekBarTimer.purge();
- mSeekBarTimer = null;
- }
- }
-
- @Override
- public void setRemoved() {
- clearTimer();
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaCallback);
- }
- if (mView instanceof MediaNotificationView) {
- ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
- mView.removeOnAttachStateChangeListener(mAttachStateListener);
- }
- }
-
- private boolean canSeekMedia(@Nullable PlaybackState state) {
- if (state == null) {
- return false;
- }
-
- long actions = state.getActions();
- return ((actions & PlaybackState.ACTION_SEEK_TO) != 0);
- }
-
- private void setScrubberVisible(boolean isVisible) {
- if (mSeekBar == null || mSeekBar.isEnabled() == isVisible) {
- return;
- }
-
- mSeekBar.getThumb().setAlpha(isVisible ? 255 : 0);
- mSeekBar.setEnabled(isVisible);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, isVisible ? 1 : 0));
- }
-
- private void updateDuration() {
- if (mMediaMetadata != null && mSeekBar != null) {
- long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (mDuration != duration) {
- mDuration = duration;
- mSeekBar.setMax((int) mDuration);
- mSeekBarTotalTime.setText(millisecondsToTimeString(duration));
- }
- }
- }
-
- protected final Runnable mOnUpdateTimerTick = new Runnable() {
- @Override
- public void run() {
- if (mMediaController != null && mSeekBar != null) {
- PlaybackState playbackState = mMediaController.getPlaybackState();
- if (playbackState != null) {
- updatePlaybackUi(playbackState);
- } else {
- clearTimer();
- }
- } else {
- clearTimer();
- }
- }
- };
-
- private void updatePlaybackUi(PlaybackState state) {
- if (mSeekBar == null || mSeekBarElapsedTime == null) {
- return;
- }
-
- long position = state.getPosition();
- mSeekBar.setProgress((int) position);
-
- mSeekBarElapsedTime.setText(millisecondsToTimeString(position));
-
- // Update scrubber in case available actions have changed
- setScrubberVisible(canSeekMedia(state));
- }
-
- private String millisecondsToTimeString(long milliseconds) {
- long seconds = milliseconds / 1000;
- String text = DateUtils.formatElapsedTime(seconds);
- return text;
}
@Override
@@ -361,28 +46,6 @@
super.onContentUpdated(row);
}
- private void updateSeekBarTint(View seekBarContainer) {
- if (seekBarContainer == null) {
- return;
- }
-
- if (this.getNotificationHeader() == null) {
- return;
- }
-
- int tintColor = getOriginalIconColor();
- mSeekBarElapsedTime.setTextColor(tintColor);
- mSeekBarTotalTime.setTextColor(tintColor);
- mSeekBarTotalTime.setShadowLayer(1.5f, 1.5f, 1.5f, mBackgroundColor);
-
- ColorStateList tintList = ColorStateList.valueOf(tintColor);
- mSeekBar.setThumbTintList(tintList);
- tintList = tintList.withAlpha(192); // 75%
- mSeekBar.setProgressTintList(tintList);
- tintList = tintList.withAlpha(128); // 50%
- mSeekBar.setProgressBackgroundTintList(tintList);
- }
-
@Override
protected void updateTransformedTypes() {
// This also clears the existing types
@@ -394,36 +57,7 @@
}
@Override
- public boolean isDimmable() {
- return getCustomBackgroundColor() == 0;
- }
-
- @Override
public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
return true;
}
-
- /**
- * Returns an initialized LogMaker for logging changes to the seekbar
- * @return new LogMaker
- */
- private LogMaker newLog(int event) {
- String packageName = mRow.getEntry().getSbn().getPackageName();
-
- return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
- .setType(event)
- .setPackageName(packageName);
- }
-
- /**
- * Returns an initialized LogMaker for logging changes with subtypes
- * @return new LogMaker
- */
- private LogMaker newLog(int event, int subtype) {
- String packageName = mRow.getEntry().getSbn().getPackageName();
- return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
- .setType(event)
- .setSubtype(subtype)
- .setPackageName(packageName);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 39f5847c..562d0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -42,6 +42,9 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -70,6 +73,8 @@
private View mOperatorNameFrame;
private CommandQueue mCommandQueue;
+ private List<String> mBlockedIcons = new ArrayList<>();
+
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setIsAirplaneMode(NetworkController.IconState icon) {
@@ -101,9 +106,12 @@
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
- mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons),
- Dependency.get(CommandQueue.class));
+ mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
mDarkIconManager.setShouldLog(true);
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_volume));
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_alarm_clock));
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_call_strength));
+ mDarkIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mClockView = mStatusBar.findViewById(R.id.clock);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 33798d6..2d760e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -47,7 +47,6 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -59,6 +58,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* The header group on Keyguard.
@@ -89,6 +90,7 @@
private int mSystemIconsBaseMargin;
private View mSystemIconsContainer;
private TintedIconManager mIconManager;
+ private List<String> mBlockedIcons = new ArrayList<>();
private View mCutoutSpace;
private ViewGroup mStatusIconArea;
@@ -121,6 +123,7 @@
mStatusIconContainer = findViewById(R.id.statusIcons);
loadDimens();
+ loadBlockList();
mBatteryController = Dependency.get(BatteryController.class);
}
@@ -181,6 +184,14 @@
R.dimen.rounded_corner_content_padding);
}
+ // Set hidden status bar items
+ private void loadBlockList() {
+ Resources r = getResources();
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_volume));
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_alarm_clock));
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_call_strength));
+ }
+
private void updateVisibilities() {
if (mMultiUserAvatar.getParent() != mStatusIconArea
&& !mKeyguardUserSwitcherEnabled) {
@@ -336,8 +347,8 @@
userInfoController.addCallback(this);
userInfoController.reloadUserInfo();
Dependency.get(ConfigurationController.class).addCallback(this);
- mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
- Dependency.get(CommandQueue.class));
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
+ mIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
onThemeChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 8fe9a48..93b83d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
@@ -37,7 +38,6 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import java.util.ArrayList;
import java.util.List;
public interface StatusBarIconController {
@@ -54,15 +55,22 @@
* When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
* in that slot and not added as a sub slot.
*/
- public static final int TAG_PRIMARY = 0;
+ int TAG_PRIMARY = 0;
- public void addIconGroup(IconManager iconManager);
- public void removeIconGroup(IconManager iconManager);
- public void setExternalIcon(String slot);
- public void setIcon(String slot, int resourceId, CharSequence contentDescription);
- public void setIcon(String slot, StatusBarIcon icon);
- public void setSignalIcon(String slot, WifiIconState state);
- public void setMobileIcons(String slot, List<MobileIconState> states);
+ /** */
+ void addIconGroup(IconManager iconManager);
+ /** */
+ void removeIconGroup(IconManager iconManager);
+ /** */
+ void setExternalIcon(String slot);
+ /** */
+ void setIcon(String slot, int resourceId, CharSequence contentDescription);
+ /** */
+ void setIcon(String slot, StatusBarIcon icon);
+ /** */
+ void setSignalIcon(String slot, WifiIconState state);
+ /** */
+ void setMobileIcons(String slot, List<MobileIconState> states);
/**
* Display the no calling & SMS icons.
*/
@@ -85,8 +93,9 @@
* If you don't know what to pass for `tag`, either remove all icons for slot, or use
* TAG_PRIMARY to refer to the first icon at a given slot.
*/
- public void removeIcon(String slot, int tag);
- public void removeAllIconsForSlot(String slot);
+ void removeIcon(String slot, int tag);
+ /** */
+ void removeAllIconsForSlot(String slot);
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
@@ -108,12 +117,12 @@
/**
* Version of ViewGroup that observes state from the DarkIconDispatcher.
*/
- public static class DarkIconManager extends IconManager {
+ class DarkIconManager extends IconManager {
private final DarkIconDispatcher mDarkIconDispatcher;
private int mIconHPadding;
- public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) {
- super(linearLayout, commandQueue);
+ public DarkIconManager(LinearLayout linearLayout) {
+ super(linearLayout);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -169,11 +178,12 @@
}
}
- public static class TintedIconManager extends IconManager {
+ /** */
+ class TintedIconManager extends IconManager {
private int mColor;
- public TintedIconManager(ViewGroup group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ public TintedIconManager(ViewGroup group) {
+ super(group);
}
@Override
@@ -219,7 +229,9 @@
private boolean mIsInDemoMode;
protected DemoStatusIcons mDemoStatusIcons;
- public IconManager(ViewGroup group, CommandQueue commandQueue) {
+ protected ArrayList<String> mBlockList = new ArrayList<>();
+
+ public IconManager(ViewGroup group) {
mGroup = group;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -234,6 +246,15 @@
mDemoable = demoable;
}
+ public void setBlockList(@Nullable List<String> blockList) {
+ mBlockList.clear();
+ if (blockList == null || blockList.isEmpty()) {
+ return;
+ }
+
+ mBlockList.addAll(blockList);
+ }
+
public void setShouldLog(boolean should) {
mShouldLog = should;
}
@@ -249,6 +270,11 @@
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
+ // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
+ // that is in our blocked list, then we'll never see it
+ if (mBlockList.contains(slot)) {
+ blocked = true;
+ }
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 6404aea..75900a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -66,6 +66,7 @@
private Context mContext;
+ /** */
@Inject
public StatusBarIconControllerImpl(
Context context,
@@ -84,6 +85,7 @@
demoModeController.addCallback(this);
}
+ /** */
@Override
public void addIconGroup(IconManager group) {
mIconGroups.add(group);
@@ -101,12 +103,14 @@
}
}
+ /** */
@Override
public void removeIconGroup(IconManager group) {
group.destroy();
mIconGroups.remove(group);
}
+ /** */
@Override
public void onTuningChanged(String key, String newValue) {
if (!ICON_HIDE_LIST.equals(key)) {
@@ -149,6 +153,7 @@
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
}
+ /** */
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
int index = getSlotIndex(slot);
@@ -290,8 +295,9 @@
* For backwards compatibility, in the event that someone gives us a slot and a status bar icon
*/
private void setIcon(int index, StatusBarIcon icon) {
+ String slot = getSlotName(index);
if (icon == null) {
- removeAllIconsForSlot(getSlotName(index));
+ removeAllIconsForSlot(slot);
return;
}
@@ -299,6 +305,7 @@
setIcon(index, holder);
}
+ /** */
@Override
public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
boolean isNew = getIcon(index, holder.getTag()) == null;
@@ -328,6 +335,7 @@
handleSet(index, holder);
}
+ /** */
@Override
public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) {
Slot slot = getSlot(slotName);
@@ -344,15 +352,18 @@
}
}
+ /** */
public void removeIcon(String slot) {
removeAllIconsForSlot(slot);
}
+ /** */
@Override
public void removeIcon(String slot, int tag) {
removeIcon(getSlotIndex(slot), tag);
}
+ /** */
@Override
public void removeAllIconsForSlot(String slotName) {
Slot slot = getSlot(slotName);
@@ -369,6 +380,7 @@
}
}
+ /** */
@Override
public void removeIcon(int index, int tag) {
if (getIcon(index, tag) == null) {
@@ -384,6 +396,7 @@
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
+ /** */
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(TAG + " state:");
@@ -402,6 +415,7 @@
super.dump(pw);
}
+ /** */
@Override
public void onDemoModeStarted() {
for (IconManager manager : mIconGroups) {
@@ -411,6 +425,7 @@
}
}
+ /** */
@Override
public void onDemoModeFinished() {
for (IconManager manager : mIconGroups) {
@@ -420,6 +435,7 @@
}
}
+ /** */
@Override
public void dispatchDemoCommand(String command, Bundle args) {
for (IconManager manager : mIconGroups) {
@@ -429,6 +445,7 @@
}
}
+ /** */
@Override
public List<String> demoCommands() {
List<String> s = new ArrayList<>();
@@ -436,6 +453,7 @@
return s;
}
+ /** */
@Override
public void onDensityOrFontScaleChanged() {
loadDimens();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 19db02a..af342dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -39,7 +39,10 @@
private MobileIconState mMobileState;
private int mType = TYPE_ICON;
private int mTag = 0;
- private boolean mVisible = true;
+
+ private StatusBarIconHolder() {
+
+ }
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
StatusBarIconHolder wrapper = new StatusBarIconHolder();
@@ -48,7 +51,10 @@
return wrapper;
}
- public static StatusBarIconHolder fromResId(Context context, int resId,
+ /** */
+ public static StatusBarIconHolder fromResId(
+ Context context,
+ int resId,
CharSequence contentDescription) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
@@ -56,6 +62,7 @@
return holder;
}
+ /** */
public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mWifiState = state;
@@ -63,6 +70,7 @@
return holder;
}
+ /** */
public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mMobileState = state;
@@ -75,7 +83,8 @@
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
public static StatusBarIconHolder fromCallIndicatorState(
- Context context, CallIndicatorIconState state) {
+ Context context,
+ CallIndicatorIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
String contentDescription = state.isNoCalling
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/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 4948c2b..3595095 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.Clock
@@ -78,8 +77,6 @@
@Mock
private lateinit var statusBarIconController: StatusBarIconController
@Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
private lateinit var demoModeController: DemoModeController
@Mock
private lateinit var userTracker: UserTracker
@@ -130,7 +127,6 @@
uiEventLogger,
qsTileHost,
statusBarIconController,
- commandQueue,
demoModeController,
userTracker,
quickQSPanelController,
@@ -233,4 +229,4 @@
`when`(privacyItemController.micCameraAvailable).thenReturn(micCamera)
`when`(privacyItemController.locationAvailable).thenReturn(location)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index e6287e7..aeb5b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -18,31 +18,18 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
import android.annotation.Nullable;
-import android.app.Notification;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.Drawable;
import android.test.suitebuilder.annotation.SmallTest;
-import android.widget.RemoteViews;
import androidx.palette.graphics.Palette;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.tests.R;
import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,17 +45,8 @@
*/
private static final int COLOR_TOLERANCE = 8;
- private MediaNotificationProcessor mProcessor;
- private Bitmap mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- private ImageGradientColorizer mColorizer;
@Nullable private Bitmap mArtwork;
- @Before
- public void setUp() {
- mColorizer = spy(new TestableColorizer(mBitmap));
- mProcessor = new MediaNotificationProcessor(getContext(), getContext(), mColorizer);
- }
-
@After
public void tearDown() {
if (mArtwork != null) {
@@ -78,53 +56,6 @@
}
@Test
- public void testColorizedWithLargeIcon() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setLargeIcon(mBitmap)
- .setContentText("Text");
- Notification notification = builder.build();
- mProcessor.processNotification(notification, builder);
- verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
- }
-
- @Test
- public void testNotColorizedWithoutLargeIcon() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
- Notification notification = builder.build();
- mProcessor.processNotification(notification, builder);
- verifyZeroInteractions(mColorizer);
- }
-
- @Test
- public void testRemoteViewsReset() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setStyle(new Notification.MediaStyle())
- .setLargeIcon(mBitmap)
- .setContentText("Text");
- Notification notification = builder.build();
- RemoteViews remoteViews = new RemoteViews(getContext().getPackageName(),
- R.layout.custom_view_dark);
- notification.contentView = remoteViews;
- notification.bigContentView = remoteViews;
- notification.headsUpContentView = remoteViews;
- mProcessor.processNotification(notification, builder);
- verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
- RemoteViews contentView = builder.createContentView();
- assertNotSame(contentView, remoteViews);
- contentView = builder.createBigContentView();
- assertNotSame(contentView, remoteViews);
- contentView = builder.createHeadsUpContentView();
- assertNotSame(contentView, remoteViews);
- }
-
- @Test
public void findBackgroundSwatch_white() {
// Given artwork that is completely white.
mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
@@ -153,17 +84,4 @@
assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
}
-
- public static class TestableColorizer extends ImageGradientColorizer {
- private final Bitmap mBitmap;
-
- private TestableColorizer(Bitmap bitmap) {
- mBitmap = bitmap;
- }
-
- @Override
- public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
- return mBitmap;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
deleted file mode 100644
index fbe4d73..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.systemui.statusbar.notification.row.wrapper;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.Notification;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.SeekBar;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase {
-
- private ExpandableNotificationRow mRow;
- private Notification mNotif;
- private View mView;
- private NotificationMediaTemplateViewWrapper mWrapper;
-
- @Mock
- private MetricsLogger mMetricsLogger;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- allowTestableLooperAsMainThread();
-
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-
- // These tests are for regular media style notifications, not controls in quick settings
- Settings.System.putInt(mContext.getContentResolver(), "qs_media_player", 0);
- }
-
- private void makeTestNotification(long duration, boolean allowSeeking) throws Exception {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
-
- MediaMetadata metadata = new MediaMetadata.Builder()
- .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
- .build();
- MediaSession session = new MediaSession(mContext, "TEST_CHANNEL");
- session.setMetadata(metadata);
-
- PlaybackState playbackState = new PlaybackState.Builder()
- .setActions(allowSeeking ? PlaybackState.ACTION_SEEK_TO : 0)
- .build();
-
- session.setPlaybackState(playbackState);
-
- builder.setStyle(new Notification.MediaStyle()
- .setMediaSession(session.getSessionToken())
- );
-
- mNotif = builder.build();
- assertTrue(mNotif.hasMediaSession());
-
- NotificationTestHelper helper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = helper.createRow(mNotif);
-
- RemoteViews views = new RemoteViews(mContext.getPackageName(),
- com.android.internal.R.layout.notification_template_material_big_media);
- mView = views.apply(mContext, null);
- mWrapper = new NotificationMediaTemplateViewWrapper(mContext,
- mView, mRow);
- mWrapper.onContentUpdated(mRow);
- }
-
- @Test
- public void testLogging_NoSeekbar() throws Exception {
- // Media sessions with duration <= 0 should not include a seekbar
- makeTestNotification(0, false);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_CLOSE
- ));
-
- verify(mMetricsLogger, times(0)).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
- }
-
- @Test
- public void testLogging_HasSeekbarNoScrubber() throws Exception {
- // Media sessions that do not support seeking should have a seekbar, but no scrubber
- makeTestNotification(1000, false);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
-
- // Ensure the callback runs at least once
- mWrapper.mOnUpdateTimerTick.run();
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_DETAIL
- && logMaker.getSubtype() == 0
- ));
- }
-
- @Test
- public void testLogging_HasSeekbarAndScrubber() throws Exception {
- makeTestNotification(1000, true);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_DETAIL
- && logMaker.getSubtype() == 1
- ));
- }
-
- @Test
- public void testLogging_UpdateSeekbar() throws Exception {
- makeTestNotification(1000, true);
-
- SeekBar seekbar = mView.findViewById(
- com.android.internal.R.id.notification_media_progress_bar);
- assertTrue(seekbar != null);
-
- mWrapper.mSeekListener.onStopTrackingTouch(seekbar);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_UPDATE));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 7b7e2d3..f147f1ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -31,7 +31,6 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -60,14 +59,14 @@
@Test
public void testSetCalledOnAdd_IconManager() {
LinearLayout layout = new LinearLayout(mContext);
- TestIconManager manager = new TestIconManager(layout, new CommandQueue(mContext));
+ TestIconManager manager = new TestIconManager(layout);
testCallOnAdd_forManager(manager);
}
@Test
public void testSetCalledOnAdd_DarkIconManager() {
LinearLayout layout = new LinearLayout(mContext);
- TestDarkIconManager manager = new TestDarkIconManager(layout, new CommandQueue(mContext));
+ TestDarkIconManager manager = new TestDarkIconManager(layout);
testCallOnAdd_forManager(manager);
}
@@ -104,8 +103,8 @@
private static class TestDarkIconManager extends DarkIconManager
implements TestableIconManager {
- TestDarkIconManager(LinearLayout group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ TestDarkIconManager(LinearLayout group) {
+ super(group);
}
@Override
@@ -139,8 +138,8 @@
}
private static class TestIconManager extends IconManager implements TestableIconManager {
- TestIconManager(ViewGroup group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ TestIconManager(ViewGroup group) {
+ super(group);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 9ac93d9..b160d78 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1205,8 +1205,6 @@
}
public void schedule() {
- Slog.d(LOG_TAG,
- "TriggerDeviceDisappearedRunnable.schedule(address = " + mAddress + ")");
mMainHandler.removeCallbacks(this);
mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
}
@@ -1244,8 +1242,6 @@
}
private void onDeviceNearby(String address) {
- Slog.i(LOG_TAG, "onDeviceNearby(address = " + address + ")");
-
Date timestamp = new Date();
Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
@@ -1259,13 +1255,11 @@
boolean justAppeared = oldTimestamp == null
|| timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
if (justAppeared) {
+ Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Device " + address
- + " managed by " + association.getPackageName()
- + " is nearby on " + timestamp);
- }
+ Slog.i(LOG_TAG,
+ "Sending onDeviceAppeared to " + association.getPackageName() + ")");
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
}
@@ -1279,12 +1273,8 @@
boolean hasDeviceListeners = false;
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Device " + address
- + " managed by " + association.getPackageName()
- + " disappeared; last seen on " + mDevicesLastNearby.get(address));
- }
-
+ Slog.i(LOG_TAG,
+ "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceDisappeared(address));
hasDeviceListeners = true;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index ed2e625..5f7016e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -222,10 +222,12 @@
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",
"java/com/android/server/connectivity/DnsManager.java",
+ "java/com/android/server/connectivity/FullScore.java",
"java/com/android/server/connectivity/KeepaliveTracker.java",
"java/com/android/server/connectivity/LingerMonitor.java",
"java/com/android/server/connectivity/MockableSystemProperties.java",
@@ -234,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/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1985848..fbba189 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);
}
/**
@@ -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
@@ -2904,10 +2905,6 @@
}
pw.println();
- pw.println("NetworkStackClient logs:");
- pw.increaseIndent();
- NetworkStackClient.getInstance().dump(pw);
- pw.decreaseIndent();
pw.println();
pw.println("Permission Monitor:");
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f04af8b..0c3d884 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -448,7 +448,7 @@
final GZIPOutputStream gzipOutputStream =
new GZIPOutputStream(new FileOutputStream(fd));
FileUtils.copy(in, gzipOutputStream);
- gzipOutputStream.finish();
+ gzipOutputStream.close();
} else {
FileUtils.copy(in, new FileOutputStream(fd));
}
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c4548a3..492759f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -182,6 +182,7 @@
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
+import android.app.job.JobParameters;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManager;
@@ -3490,7 +3491,9 @@
// Clear its scheduled jobs
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
- js.cancelJobsForUid(appInfo.uid, "clear data");
+ // Clearing data is akin to uninstalling. The app is force stopped before we
+ // get to this point, so the reason won't be checked by the app.
+ js.cancelJobsForUid(appInfo.uid, JobParameters.STOP_REASON_USER, "clear data");
// Clear its pending alarms
AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index d8d6528..70a54cf 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -143,27 +143,45 @@
* Request that autofill data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
*/
public void requestAutofillData(List<IBinder> activityTokens, int callingUid,
String callingPackage) {
requestData(activityTokens, true /* requestAutofillData */,
true /* fetchData */, false /* fetchScreenshot */,
true /* allowFetchData */, false /* allowFetchScreenshot */,
- callingUid, callingPackage);
+ false /* ignoreTopActivityCheck */, callingUid, callingPackage);
}
/**
* Request that assist data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
*/
public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
int callingUid, String callingPackage) {
+ requestAssistData(activityTokens, fetchData, fetchScreenshot, allowFetchData,
+ allowFetchScreenshot, false /* ignoreTopActivityCheck */, callingUid,
+ callingPackage);
+ }
+
+ /**
+ * Request that assist data be loaded asynchronously. The resulting data will be provided
+ * through the {@link AssistDataRequesterCallbacks}.
+ *
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
+ */
+ public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+ final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
+ boolean ignoreTopActivityCheck, int callingUid, String callingPackage) {
requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot,
- allowFetchData, allowFetchScreenshot, callingUid, callingPackage);
+ allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck, callingUid,
+ callingPackage);
}
/**
@@ -183,10 +201,13 @@
* is allowed to fetch the assist data
* @param allowFetchScreenshot to be joined with other checks, determines whether or not the
* requester is allowed to fetch the assist screenshot
+ * @param ignoreTopActivityCheck overrides the check for whether the activity is in focus when
+ * making the request. Used when passing an activity from Recents.
*/
private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData,
final boolean fetchData, final boolean fetchScreenshot, boolean allowFetchData,
- boolean allowFetchScreenshot, int callingUid, String callingPackage) {
+ boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
+ String callingPackage) {
// TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
// then no assist data is requested for any of the other activities
@@ -230,7 +251,8 @@
receiverExtras, topActivity, 0 /* flags */)
: mActivityTaskManager.requestAssistContextExtras(
ASSIST_CONTEXT_FULL, this, receiverExtras, topActivity,
- /* focused= */ i == 0, /* newSessionId= */ i == 0);
+ /* checkActivityIsTop= */ (i == 0)
+ && !ignoreTopActivityCheck, /* newSessionId= */ i == 0);
if (result) {
mPendingDataCount++;
} else if (i == 0) {
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index 4c9ab63..d789bbb 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -241,7 +241,7 @@
/**
* For a consumer of type {@link EnergyConsumerType#OTHER}, updates
* {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
- * charge consumed (in microcouloumbs) between the previously stored values and the passed-in
+ * charge consumed (in microcoulombs) between the previously stored values and the passed-in
* values.
*
* @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
@@ -341,11 +341,11 @@
return numOrdinals;
}
- /** Calculate charge consumption (in microcouloumbs) from a given energy and voltage */
+ /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
private long calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV) {
// To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
- // since the last snapshot. Round up to the nearest whole long.
- return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV + 1) / 2) / avgVoltageMV;
+ // since the last snapshot. Round off to the nearest whole long.
+ return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
}
}
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..8b9be83
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+public abstract class StartUserClient<T> extends HalClientMonitor<T> {
+
+ public interface UserStartedCallback {
+ void onUserStarted(int newUserId);
+ }
+
+ @NonNull @VisibleForTesting
+ protected final UserStartedCallback mUserStartedCallback;
+
+ public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull UserStartedCallback 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..62cd673
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+public abstract class StopUserClient<T> extends HalClientMonitor<T> {
+
+ public interface UserStoppedCallback {
+ void onUserStopped();
+ }
+
+ @NonNull @VisibleForTesting
+ protected final UserStoppedCallback mUserStoppedCallback;
+
+ 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..c0ea2b3
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+ @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) {
+ Slog.d(getTag(), "User switch required, current user null, next: " + nextUserId);
+ final BaseClientMonitor startClient =
+ mUserSwitchCallback.getStartUserClient(nextUserId);
+ startClient.start(mClientFinishedCallback);
+ } else {
+ final BaseClientMonitor stopClient = mUserSwitchCallback
+ .getStopUserClient(currentUserId);
+ Slog.d(getTag(), "User switch required, current: " + currentUserId
+ + ", next: " + nextUserId + ". " + stopClient);
+ stopClient.start(mClientFinishedCallback);
+ }
+ }
+}
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..fdc3bb1 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
@@ -254,7 +254,7 @@
mContext.getOpPackageName(), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
+ ", sensorId: " + sensorId
@@ -268,7 +268,7 @@
final InvalidationRequesterClient<Face> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
FaceUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@@ -318,7 +318,7 @@
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception", e);
}
@@ -468,7 +468,7 @@
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
}
@@ -523,7 +523,7 @@
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling remove", e);
}
@@ -599,7 +599,7 @@
FaceUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
}
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 c6f39aa..3eb4759 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
@@ -424,6 +424,12 @@
});
}
+ @Override
+ public void onSessionClosed() {
+ mHandler.post(() -> {
+ // TODO: implement this.
+ });
+ }
}
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
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..bcca69b 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
@@ -291,7 +291,7 @@
mSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
+ ", sensorId: " + sensorId
@@ -305,7 +305,7 @@
final InvalidationRequesterClient<Fingerprint> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
FingerprintUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@@ -458,7 +458,7 @@
mSensors.get(sensorId).getLazySession(), token, callback, userId,
opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling finger detect", e);
}
@@ -492,7 +492,7 @@
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, allowBackgroundAuthentication);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
}
@@ -554,7 +554,7 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling remove", e);
}
@@ -583,7 +583,7 @@
mContext.getOpPackageName(), sensorId, enrolledList,
FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
}
@@ -627,7 +627,7 @@
new FingerprintInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception", e);
}
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 5631647..d843bc9 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
@@ -403,6 +403,13 @@
invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
});
}
+
+ @Override
+ public void onSessionClosed() {
+ mHandler.post(() -> {
+ // TODO: implement this.
+ });
+ }
}
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 71565301..781bad7 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -106,16 +106,19 @@
return bits;
}
- private void openPipe() {
+ private boolean openPipe() {
try {
- mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
- mPipe.write(createOpenHandshake());
- } catch (IOException e) {
+ final RandomAccessFile pipe = new RandomAccessFile(PIPE_DEVICE, "rw");
try {
- if (mPipe != null) mPipe.close();
- } catch (IOException ee) {}
- mPipe = null;
+ pipe.write(createOpenHandshake());
+ mPipe = pipe;
+ return true;
+ } catch (IOException ignore) {
+ pipe.close();
+ }
+ } catch (IOException ignore) {
}
+ return false;
}
public HostClipboardMonitor(HostClipboardCallback cb) {
@@ -129,8 +132,7 @@
// There's no guarantee that QEMU pipes will be ready at the moment
// this method is invoked. We simply try to get the pipe open and
// retry on failure indefinitely.
- while (mPipe == null) {
- openPipe();
+ while ((mPipe == null) && !openPipe()) {
Thread.sleep(100);
}
int size = mPipe.readInt();
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 74e4ae7..acf39f0 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -26,6 +26,7 @@
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_NO_SUCH_SLOT;
import static android.net.SocketKeepalive.ERROR_STOP_REASON_UNINITIALIZED;
import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
@@ -528,6 +529,8 @@
}
} else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
throw new IllegalStateException("Unexpected stop reason: " + reason);
+ } else if (reason == ERROR_NO_SUCH_SLOT) {
+ throw new IllegalStateException("No such slot: " + reason);
} else {
notifyErrorCallback(ki.mCallback, reason);
}
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/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..70401b4 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1858,22 +1858,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/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index aaf9cbc..1f46061 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -119,7 +119,7 @@
public boolean onStopJob(JobParameters params) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
- + params.getStopReason());
+ + params.getLegacyStopReason());
}
final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
if (op == null) {
@@ -161,9 +161,9 @@
m.obj = op;
// Reschedule if this job was NOT explicitly canceled.
- m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
+ m.arg1 = params.getLegacyStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
// Apply backoff only if stop is called due to timeout.
- m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
+ m.arg2 = params.getLegacyStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
SyncManager.sendMessage(m);
return false;
@@ -204,7 +204,8 @@
return "job:null";
} else {
return "job:#" + params.getJobId() + ":"
- + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:"
+ + "sr=[" + params.getLegacyStopReason()
+ + "/" + params.getDebugStopReason() + "]:"
+ SyncOperation.maybeCreateFromJobExtras(params.getExtras());
}
}
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 2920ddb..864aa33 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -31,6 +31,7 @@
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -158,10 +159,9 @@
public Lifecycle(Context context) {
super(context);
- LocationEventLog eventLog = new LocationEventLog();
mUserInfoHelper = new LifecycleUserInfoHelper(context);
- mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog);
- mService = new LocationManagerService(context, mSystemInjector, eventLog);
+ mSystemInjector = new SystemInjector(context, mUserInfoHelper);
+ mService = new LocationManagerService(context, mSystemInjector);
}
@Override
@@ -233,7 +233,6 @@
private final Context mContext;
private final Injector mInjector;
- private final LocationEventLog mEventLog;
private final LocalService mLocalService;
private final GeofenceManager mGeofenceManager;
@@ -261,18 +260,20 @@
@GuardedBy("mLock")
private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener;
- LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) {
+ LocationManagerService(Context context, Injector injector) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mInjector = injector;
- mEventLog = eventLog;
mLocalService = new LocalService();
LocalServices.addService(LocationManagerInternal.class, mLocalService);
mGeofenceManager = new GeofenceManager(mContext, injector);
+ mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
+ this::onLocationModeChanged);
+
// set up passive provider first since it will be required for all other location providers,
// which are loaded later once the system is ready.
- mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog);
+ mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext));
// TODO: load the gps provider here as well, which will require refactoring
@@ -313,7 +314,7 @@
}
LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
- mEventLog, providerName, mPassiveManager);
+ providerName, mPassiveManager);
addLocationProviderManager(manager, null);
return manager;
}
@@ -335,7 +336,7 @@
Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
if (enableStationaryThrottling) {
realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
- mInjector, realProvider, mEventLog);
+ mInjector, realProvider);
}
}
manager.setRealProvider(realProvider);
@@ -355,9 +356,6 @@
}
void onSystemReady() {
- mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
- this::onLocationModeChanged);
-
if (Build.IS_DEBUGGABLE) {
// on debug builds, watch for location noteOps while location is off. there are some
// scenarios (emergency location) where this is expected, but generally this should
@@ -380,12 +378,13 @@
// 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);
if (networkProvider != null) {
LocationProviderManager networkManager = new LocationProviderManager(mContext,
- mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager);
+ mInjector, NETWORK_PROVIDER, mPassiveManager);
addLocationProviderManager(networkManager, networkProvider);
} else {
Log.w(TAG, "no network location provider found");
@@ -399,12 +398,13 @@
ProxyLocationProvider fusedProvider = ProxyLocationProvider.create(
mContext,
+ FUSED_PROVIDER,
ACTION_FUSED_PROVIDER,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName);
if (fusedProvider != null) {
LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
- mEventLog, FUSED_PROVIDER, mPassiveManager);
+ FUSED_PROVIDER, mPassiveManager);
addLocationProviderManager(fusedManager, fusedProvider);
} else {
Log.wtf(TAG, "no fused location provider found");
@@ -419,7 +419,7 @@
mGnssManagerService.onSystemReady();
LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
- mEventLog, GPS_PROVIDER, mPassiveManager);
+ GPS_PROVIDER, mPassiveManager);
addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
}
@@ -476,7 +476,7 @@
Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
}
- mEventLog.logLocationEnabled(userId, enabled);
+ EVENT_LOG.logLocationEnabled(userId, enabled);
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -1268,7 +1268,7 @@
ipw.println("Event Log:");
ipw.increaseIndent();
- mEventLog.iterate(manager.getName(), ipw::println);
+ EVENT_LOG.iterate(manager.getName(), ipw::println);
ipw.decreaseIndent();
return;
}
@@ -1313,7 +1313,7 @@
ipw.println("Historical Aggregate Location Provider Data:");
ipw.increaseIndent();
ArrayMap<String, ArrayMap<CallerIdentity, LocationEventLog.AggregateStats>> aggregateStats =
- mEventLog.copyAggregateStats();
+ EVENT_LOG.copyAggregateStats();
for (int i = 0; i < aggregateStats.size(); i++) {
ipw.print(aggregateStats.keyAt(i));
ipw.println(":");
@@ -1344,7 +1344,7 @@
ipw.println("Event Log:");
ipw.increaseIndent();
- mEventLog.iterate(ipw::println);
+ EVENT_LOG.iterate(ipw::println);
ipw.decreaseIndent();
}
@@ -1456,7 +1456,7 @@
@GuardedBy("this")
private boolean mSystemReady;
- SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) {
+ SystemInjector(Context context, UserInfoHelper userInfoHelper) {
mContext = context;
mUserInfoHelper = userInfoHelper;
@@ -1466,7 +1466,7 @@
mAppOpsHelper);
mSettingsHelper = new SystemSettingsHelper(context);
mAppForegroundHelper = new SystemAppForegroundHelper(context);
- mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
+ mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context);
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 29ce378..045e06d0 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -44,6 +44,8 @@
/** In memory event log for location events. */
public class LocationEventLog extends LocalEventLog {
+ public static final LocationEventLog EVENT_LOG = new LocationEventLog();
+
private static int getLogSize() {
if (Build.IS_DEBUGGABLE || D) {
return 500;
@@ -52,16 +54,17 @@
}
}
- private static final int EVENT_LOCATION_ENABLED = 1;
- private static final int EVENT_PROVIDER_ENABLED = 2;
- private static final int EVENT_PROVIDER_MOCKED = 3;
- private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
- private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
- private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
- private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
- private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
- private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
- private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
+ private static final int EVENT_USER_SWITCHED = 1;
+ private static final int EVENT_LOCATION_ENABLED = 2;
+ private static final int EVENT_PROVIDER_ENABLED = 3;
+ private static final int EVENT_PROVIDER_MOCKED = 4;
+ private static final int EVENT_PROVIDER_REGISTER_CLIENT = 5;
+ private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 6;
+ private static final int EVENT_PROVIDER_UPDATE_REQUEST = 7;
+ private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 8;
+ private static final int EVENT_PROVIDER_DELIVER_LOCATION = 9;
+ private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 10;
+ private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 11;
@GuardedBy("mAggregateStats")
private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
@@ -90,19 +93,24 @@
packageMap = new ArrayMap<>(2);
mAggregateStats.put(provider, packageMap);
}
- CallerIdentity stripped = identity.stripListenerId();
- AggregateStats stats = packageMap.get(stripped);
+ CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
+ AggregateStats stats = packageMap.get(aggregate);
if (stats == null) {
stats = new AggregateStats();
- packageMap.put(stripped, stats);
+ packageMap.put(aggregate, stats);
}
return stats;
}
}
+ /** Logs a user switched event. */
+ public void logUserSwitched(int userIdFrom, int userIdTo) {
+ addLogEvent(EVENT_USER_SWITCHED, userIdFrom, userIdTo);
+ }
+
/** Logs a location enabled/disabled event. */
public void logLocationEnabled(int userId, boolean enabled) {
- addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+ addLogEvent(EVENT_LOCATION_ENABLED, userId, enabled);
}
/** Logs a location provider enabled/disabled event. */
@@ -183,8 +191,10 @@
@Override
protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
switch (event) {
+ case EVENT_USER_SWITCHED:
+ return new UserSwitchedEvent(timeDelta, (Integer) args[0], (Integer) args[1]);
case EVENT_LOCATION_ENABLED:
- return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+ return new LocationEnabledEvent(timeDelta, (Integer) args[0], (Boolean) args[1]);
case EVENT_PROVIDER_ENABLED:
return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
(Boolean) args[2]);
@@ -397,6 +407,23 @@
}
}
+ private static final class UserSwitchedEvent extends LogEvent {
+
+ private final int mUserIdFrom;
+ private final int mUserIdTo;
+
+ UserSwitchedEvent(long timeDelta, int userIdFrom, int userIdTo) {
+ super(timeDelta);
+ mUserIdFrom = userIdFrom;
+ mUserIdTo = userIdTo;
+ }
+
+ @Override
+ public String getLogString() {
+ return "current user switched from u" + mUserIdFrom + " to u" + mUserIdTo;
+ }
+ }
+
private static final class LocationEnabledEvent extends LogEvent {
private final int mUserId;
@@ -410,7 +437,7 @@
@Override
public String getLogString() {
- return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+ return "location [u" + mUserId + "] " + (mEnabled ? "enabled" : "disabled");
}
}
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/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
index cc00d56..53407d9 100644
--- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
@@ -20,12 +20,11 @@
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import android.os.PowerManager.LocationPowerSaveMode;
import android.util.Log;
-import com.android.server.location.eventlog.LocationEventLog;
-
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -43,11 +42,9 @@
void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode);
}
- private final LocationEventLog mLocationEventLog;
private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners;
- public LocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
- mLocationEventLog = locationEventLog;
+ public LocationPowerSaveModeHelper() {
mListeners = new CopyOnWriteArrayList<>();
}
@@ -72,7 +69,7 @@
Log.d(TAG, "location power save mode is now " + locationPowerSaveModeToString(
locationPowerSaveMode));
}
- mLocationEventLog.logLocationPowerSaveMode(locationPowerSaveMode);
+ EVENT_LOG.logLocationPowerSaveMode(locationPowerSaveMode);
for (LocationPowerSaveModeChangedListener listener : mListeners) {
listener.onLocationPowerSaveModeChanged(locationPowerSaveMode);
diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
index c47a64d..a675f54 100644
--- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
@@ -25,7 +25,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import java.util.Objects;
import java.util.function.Consumer;
@@ -42,8 +41,7 @@
@LocationPowerSaveMode
private volatile int mLocationPowerSaveMode;
- public SystemLocationPowerSaveModeHelper(Context context, LocationEventLog locationEventLog) {
- super(locationEventLog);
+ public SystemLocationPowerSaveModeHelper(Context context) {
mContext = context;
}
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3f7345e..632ed6e 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -35,6 +35,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -348,31 +349,73 @@
*/
@Override
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
- int userId = ActivityManager.getCurrentUser();
+ int[] userIds;
+ try {
+ userIds = ActivityManager.getService().getRunningUserIds();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
- ipw.print("Location Enabled: ");
- ipw.println(isLocationEnabled(userId));
-
- List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(userId);
- if (!locationPackageBlacklist.isEmpty()) {
- ipw.println("Location Deny Packages:");
- ipw.increaseIndent();
- for (String packageName : locationPackageBlacklist) {
- ipw.println(packageName);
+ ipw.print("Location Setting: ");
+ ipw.increaseIndent();
+ if (userIds.length > 1) {
+ ipw.println();
+ for (int userId : userIds) {
+ ipw.print("[u");
+ ipw.print(userId);
+ ipw.print("] ");
+ ipw.println(isLocationEnabled(userId));
}
- ipw.decreaseIndent();
+ } else {
+ ipw.println(isLocationEnabled(userIds[0]));
+ }
+ ipw.decreaseIndent();
- List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
- userId);
- if (!locationPackageWhitelist.isEmpty()) {
- ipw.println("Location Allow Packages:");
+ ipw.println("Location Allow/Deny Packages:");
+ ipw.increaseIndent();
+ if (userIds.length > 1) {
+ for (int userId : userIds) {
+ List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+ userId);
+ if (locationPackageBlacklist.isEmpty()) {
+ continue;
+ }
+
+ ipw.print("user ");
+ ipw.print(userId);
+ ipw.println(":");
ipw.increaseIndent();
- for (String packageName : locationPackageWhitelist) {
+
+ for (String packageName : locationPackageBlacklist) {
+ ipw.print("[deny] ");
ipw.println(packageName);
}
+
+ List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+ userId);
+ for (String packageName : locationPackageWhitelist) {
+ ipw.print("[allow] ");
+ ipw.println(packageName);
+ }
+
ipw.decreaseIndent();
}
+ } else {
+ List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+ userIds[0]);
+ for (String packageName : locationPackageBlacklist) {
+ ipw.print("[deny] ");
+ ipw.println(packageName);
+ }
+
+ List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+ userIds[0]);
+ for (String packageName : locationPackageWhitelist) {
+ ipw.print("[allow] ");
+ ipw.println(packageName);
+ }
}
+ ipw.decreaseIndent();
Set<String> backgroundThrottlePackageWhitelist =
mBackgroundThrottlePackageWhitelist.getValue();
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index d4a8fbd..ed1e654 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -155,6 +155,11 @@
*/
@Override
public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ int[] runningUserIds = getRunningUserIds();
+ if (runningUserIds.length > 1) {
+ pw.println("running users: u" + Arrays.toString(runningUserIds));
+ }
+
ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
if (activityManagerInternal == null) {
return;
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index 0fcc1ec..c835370 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -18,6 +18,7 @@
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
@@ -105,6 +106,7 @@
Log.d(TAG, "current user changed from u" + Arrays.toString(fromUserIds) + " to u"
+ Arrays.toString(toUserIds));
}
+ EVENT_LOG.logUserSwitched(fromUserId, toUserId);
for (UserListener listener : mListeners) {
for (int userId : fromUserIds) {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index dc8b1d0..102263b 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -37,6 +37,7 @@
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -94,7 +95,6 @@
import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.fudger.LocationFudger;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
@@ -333,7 +333,7 @@
+ getRequest());
}
- mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+ EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -345,7 +345,7 @@
onProviderListenerRegister();
if (mForeground) {
- mEventLog.logProviderClientForeground(mName, getIdentity());
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
}
}
@@ -358,7 +358,7 @@
onProviderListenerUnregister();
- mEventLog.logProviderClientUnregistered(mName, getIdentity());
+ EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
@@ -383,7 +383,7 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
- mEventLog.logProviderClientActive(mName, getIdentity());
+ EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
@@ -406,7 +406,7 @@
onProviderListenerInactive();
- mEventLog.logProviderClientInactive(mName, getIdentity());
+ EVENT_LOG.logProviderClientInactive(mName, getIdentity());
}
/**
@@ -543,9 +543,9 @@
mForeground = foreground;
if (mForeground) {
- mEventLog.logProviderClientForeground(mName, getIdentity());
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
} else {
- mEventLog.logProviderClientBackground(mName, getIdentity());
+ EVENT_LOG.logProviderClientBackground(mName, getIdentity());
}
// note that onProviderLocationRequestChanged() is always called
@@ -654,7 +654,7 @@
protected abstract class LocationRegistration extends Registration implements
OnAlarmListener, ProviderEnabledListener {
- private final PowerManager.WakeLock mWakeLock;
+ final PowerManager.WakeLock mWakeLock;
private volatile ProviderTransport mProviderTransport;
private int mNumLocationsDelivered = 0;
@@ -879,7 +879,7 @@
listener.deliverOnLocationChanged(deliverLocationResult,
mUseWakeLock ? mWakeLock::release : null);
- mEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
+ EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
getIdentity());
}
@@ -1178,7 +1178,7 @@
// we currently don't hold a wakelock for getCurrentLocation deliveries
listener.deliverOnLocationChanged(deliverLocationResult, null);
- mEventLog.logProviderDeliveredLocations(mName,
+ EVENT_LOG.logProviderDeliveredLocations(mName,
locationResult != null ? locationResult.size() : 0, getIdentity());
}
@@ -1247,7 +1247,6 @@
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
- protected final LocationEventLog mEventLog;
protected final LocationManagerInternal mLocationManagerInternal;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserHelper;
@@ -1300,7 +1299,7 @@
@GuardedBy("mLock")
private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener;
- public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog,
+ public LocationProviderManager(Context context, Injector injector,
String name, @Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
mName = Objects.requireNonNull(name);
@@ -1312,7 +1311,6 @@
mEnabledListeners = new ArrayList<>();
mProviderRequestListeners = new CopyOnWriteArrayList<>();
- mEventLog = eventLog;
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mSettingsHelper = injector.getSettingsHelper();
@@ -1477,7 +1475,7 @@
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
- mEventLog.logProviderMocked(mName, provider != null);
+ EVENT_LOG.logProviderMocked(mName, provider != null);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1966,8 +1964,8 @@
}
@GuardedBy("mLock")
- private void setProviderRequest(ProviderRequest request) {
- mEventLog.logProviderUpdateRequest(mName, request);
+ void setProviderRequest(ProviderRequest request) {
+ EVENT_LOG.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
FgThread.getHandler().post(() -> {
@@ -2324,7 +2322,7 @@
}
// don't log location received for passive provider because it's spammy
- mEventLog.logProviderReceivedLocations(mName, filtered.size());
+ EVENT_LOG.logProviderReceivedLocations(mName, filtered.size());
} else {
// passive provider should get already filtered results as input
filtered = locationResult;
@@ -2424,7 +2422,7 @@
if (D) {
Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
}
- mEventLog.logProviderEnabled(mName, userId, enabled);
+ EVENT_LOG.logProviderEnabled(mName, userId, enabled);
}
// clear last locations if we become disabled
@@ -2464,7 +2462,7 @@
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- private @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
+ @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
@PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2477,7 +2475,7 @@
}
}
- private @Nullable LocationResult getPermittedLocationResult(
+ @Nullable LocationResult getPermittedLocationResult(
@Nullable LocationResult fineLocationResult, @PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2538,6 +2536,8 @@
private @Nullable Location mFineBypassLocation;
private @Nullable Location mCoarseBypassLocation;
+ LastLocation() {}
+
public void clearMock() {
if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
mFineLocation = null;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index 027f4e9..b35af4f 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -24,7 +24,6 @@
import android.os.Binder;
import com.android.internal.util.Preconditions;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.Injector;
import java.util.Collection;
@@ -34,9 +33,8 @@
*/
public class PassiveLocationProviderManager extends LocationProviderManager {
- public PassiveLocationProviderManager(Context context, Injector injector,
- LocationEventLog eventLog) {
- super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null);
+ public PassiveLocationProviderManager(Context context, Injector injector) {
+ super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 6f4aa64..ab7e526 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import android.annotation.Nullable;
import android.location.Location;
@@ -33,7 +34,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.DeviceIdleHelper;
import com.android.server.location.injector.DeviceStationaryHelper;
import com.android.server.location.injector.Injector;
@@ -54,12 +54,11 @@
private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
- private final Object mLock = new Object();
+ final Object mLock = new Object();
private final String mName;
private final DeviceIdleHelper mDeviceIdleHelper;
private final DeviceStationaryHelper mDeviceStationaryHelper;
- private final LocationEventLog mEventLog;
@GuardedBy("mLock")
private boolean mDeviceIdle = false;
@@ -72,21 +71,19 @@
@GuardedBy("mLock")
private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
@GuardedBy("mLock")
- private long mThrottlingIntervalMs = INTERVAL_DISABLED;
+ long mThrottlingIntervalMs = INTERVAL_DISABLED;
@GuardedBy("mLock")
- private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
-
+ @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
@GuardedBy("mLock")
- private @Nullable Location mLastLocation;
+ @Nullable Location mLastLocation;
public StationaryThrottlingLocationProvider(String name, Injector injector,
- AbstractLocationProvider delegate, LocationEventLog eventLog) {
+ AbstractLocationProvider delegate) {
super(DIRECT_EXECUTOR, delegate);
mName = name;
mDeviceIdleHelper = injector.getDeviceIdleHelper();
mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
- mEventLog = eventLog;
// must be last statement in the constructor because reference is escaping
initializeDelegate();
@@ -209,7 +206,7 @@
if (D) {
Log.d(TAG, mName + " provider stationary throttled");
}
- mEventLog.logProviderStationaryThrottled(mName, true);
+ EVENT_LOG.logProviderStationaryThrottled(mName, true);
}
if (mDeliverLastLocationCallback != null) {
@@ -227,7 +224,7 @@
}
} else {
if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
- mEventLog.logProviderStationaryThrottled(mName, false);
+ EVENT_LOG.logProviderStationaryThrottled(mName, false);
if (D) {
Log.d(TAG, mName + " provider stationary unthrottled");
}
@@ -257,6 +254,9 @@
}
private class DeliverLastLocationRunnable implements Runnable {
+
+ DeliverLastLocationRunnable() {}
+
@Override
public void run() {
Location location;
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 c86e49b..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,42 +27,46 @@
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;
/**
* Creates and registers this proxy. If no suitable service is available for the proxy, returns
* 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 {
@@ -80,21 +83,24 @@
final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0);
@GuardedBy("mLock")
- Proxy mProxy;
+ @Nullable Runnable mResetter;
@GuardedBy("mLock")
- @Nullable ComponentName mService;
+ @Nullable Proxy mProxy;
+ @GuardedBy("mLock")
+ @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;
@@ -104,21 +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;
-
- // 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);
@@ -129,12 +127,30 @@
}
}
- private void onUnbind() {
+ @Override
+ public void onUnbind() {
Runnable[] flushListeners;
synchronized (mLock) {
mProxy = null;
- mService = null;
- setState(prevState -> State.EMPTY_STATE);
+ 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 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);
+ }
+
flushListeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
@@ -166,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);
@@ -206,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 {
@@ -229,26 +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;
}
- setState(prevState -> prevState
+ // 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
.withAllowed(allowed)
.withProperties(properties)
- .withIdentity(identity));
+ .withIdentity(identity)
+ .withExtraAttributionTags(extraAttributionTags));
}
}
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index cc3a002..df1eb6d 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -22,7 +22,6 @@
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
import android.net.ProxyInfo;
-import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
import android.util.ArrayMap;
@@ -42,6 +41,8 @@
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
public class IpConfigStore {
private static final String TAG = "IpConfigStore";
@@ -83,25 +84,25 @@
boolean written = false;
try {
- switch (config.ipAssignment) {
+ switch (config.getIpAssignment()) {
case STATIC:
out.writeUTF(IP_ASSIGNMENT_KEY);
- out.writeUTF(config.ipAssignment.toString());
- StaticIpConfiguration staticIpConfiguration = config.staticIpConfiguration;
+ out.writeUTF(config.getIpAssignment().toString());
+ StaticIpConfiguration staticIpConfiguration = config.getStaticIpConfiguration();
if (staticIpConfiguration != null) {
- if (staticIpConfiguration.ipAddress != null) {
- LinkAddress ipAddress = staticIpConfiguration.ipAddress;
+ if (staticIpConfiguration.getIpAddress() != null) {
+ LinkAddress ipAddress = staticIpConfiguration.getIpAddress();
out.writeUTF(LINK_ADDRESS_KEY);
out.writeUTF(ipAddress.getAddress().getHostAddress());
out.writeInt(ipAddress.getPrefixLength());
}
- if (staticIpConfiguration.gateway != null) {
+ if (staticIpConfiguration.getGateway() != null) {
out.writeUTF(GATEWAY_KEY);
out.writeInt(0); // Default route.
out.writeInt(1); // Have a gateway.
- out.writeUTF(staticIpConfiguration.gateway.getHostAddress());
+ out.writeUTF(staticIpConfiguration.getGateway().getHostAddress());
}
- for (InetAddress inetAddr : staticIpConfiguration.dnsServers) {
+ for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
out.writeUTF(DNS_KEY);
out.writeUTF(inetAddr.getHostAddress());
}
@@ -110,7 +111,7 @@
break;
case DHCP:
out.writeUTF(IP_ASSIGNMENT_KEY);
- out.writeUTF(config.ipAssignment.toString());
+ out.writeUTF(config.getIpAssignment().toString());
written = true;
break;
case UNASSIGNED:
@@ -121,13 +122,13 @@
break;
}
- switch (config.proxySettings) {
+ switch (config.getProxySettings()) {
case STATIC:
- ProxyInfo proxyProperties = config.httpProxy;
+ ProxyInfo proxyProperties = config.getHttpProxy();
String exclusionList = ProxyUtils.exclusionListAsString(
proxyProperties.getExclusionList());
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
out.writeUTF(PROXY_HOST_KEY);
out.writeUTF(proxyProperties.getHost());
out.writeUTF(PROXY_PORT_KEY);
@@ -139,16 +140,16 @@
written = true;
break;
case PAC:
- ProxyInfo proxyPacProperties = config.httpProxy;
+ ProxyInfo proxyPacProperties = config.getHttpProxy();
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
out.writeUTF(PROXY_PAC_FILE);
out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
written = true;
break;
case NONE:
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
written = true;
break;
case UNASSIGNED:
@@ -267,11 +268,14 @@
IpAssignment ipAssignment = IpAssignment.DHCP;
ProxySettings proxySettings = ProxySettings.NONE;
StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+ LinkAddress linkAddress = null;
+ InetAddress gatewayAddress = null;
String proxyHost = null;
String pacFileUrl = null;
int proxyPort = -1;
String exclusionList = null;
String key;
+ final List<InetAddress> dnsServers = new ArrayList<>();
do {
key = in.readUTF();
@@ -286,15 +290,15 @@
} else if (key.equals(IP_ASSIGNMENT_KEY)) {
ipAssignment = IpAssignment.valueOf(in.readUTF());
} else if (key.equals(LINK_ADDRESS_KEY)) {
- LinkAddress linkAddr =
+ LinkAddress parsedLinkAddress =
new LinkAddress(
InetAddresses.parseNumericAddress(in.readUTF()),
in.readInt());
- if (linkAddr.getAddress() instanceof Inet4Address &&
- staticIpConfiguration.ipAddress == null) {
- staticIpConfiguration.ipAddress = linkAddr;
+ if (parsedLinkAddress.getAddress() instanceof Inet4Address
+ && linkAddress == null) {
+ linkAddress = parsedLinkAddress;
} else {
- loge("Non-IPv4 or duplicate address: " + linkAddr);
+ loge("Non-IPv4 or duplicate address: " + parsedLinkAddress);
}
} else if (key.equals(GATEWAY_KEY)) {
LinkAddress dest = null;
@@ -302,8 +306,8 @@
if (version == 1) {
// only supported default gateways - leave the dest/prefix empty
gateway = InetAddresses.parseNumericAddress(in.readUTF());
- if (staticIpConfiguration.gateway == null) {
- staticIpConfiguration.gateway = gateway;
+ if (gatewayAddress == null) {
+ gatewayAddress = gateway;
} else {
loge("Duplicate gateway: " + gateway.getHostAddress());
}
@@ -317,17 +321,18 @@
if (in.readInt() == 1) {
gateway = InetAddresses.parseNumericAddress(in.readUTF());
}
- RouteInfo route = new RouteInfo(dest, gateway);
- if (route.isIPv4Default() &&
- staticIpConfiguration.gateway == null) {
- staticIpConfiguration.gateway = gateway;
+ // If the destination is a default IPv4 route, use the gateway
+ // address unless already set.
+ if (dest.getAddress() instanceof Inet4Address
+ && dest.getPrefixLength() == 0 && gatewayAddress == null) {
+ gatewayAddress = gateway;
} else {
- loge("Non-IPv4 default or duplicate route: " + route);
+ loge("Non-IPv4 default or duplicate route: "
+ + dest.getAddress());
}
}
} else if (key.equals(DNS_KEY)) {
- staticIpConfiguration.dnsServers.add(
- InetAddresses.parseNumericAddress(in.readUTF()));
+ dnsServers.add(InetAddresses.parseNumericAddress(in.readUTF()));
} else if (key.equals(PROXY_SETTINGS_KEY)) {
proxySettings = ProxySettings.valueOf(in.readUTF());
} else if (key.equals(PROXY_HOST_KEY)) {
@@ -348,25 +353,31 @@
}
} while (true);
+ staticIpConfiguration = new StaticIpConfiguration.Builder()
+ .setIpAddress(linkAddress)
+ .setGateway(gatewayAddress)
+ .setDnsServers(dnsServers)
+ .build();
+
if (uniqueToken != null) {
IpConfiguration config = new IpConfiguration();
networks.put(uniqueToken, config);
switch (ipAssignment) {
case STATIC:
- config.staticIpConfiguration = staticIpConfiguration;
- config.ipAssignment = ipAssignment;
+ config.setStaticIpConfiguration(staticIpConfiguration);
+ config.setIpAssignment(ipAssignment);
break;
case DHCP:
- config.ipAssignment = ipAssignment;
+ config.setIpAssignment(ipAssignment);
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED IP on file, use DHCP");
- config.ipAssignment = IpAssignment.DHCP;
+ config.setIpAssignment(IpAssignment.DHCP);
break;
default:
loge("Ignore invalid ip assignment while reading.");
- config.ipAssignment = IpAssignment.UNASSIGNED;
+ config.setIpAssignment(IpAssignment.UNASSIGNED);
break;
}
@@ -374,25 +385,25 @@
case STATIC:
ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
ProxyUtils.exclusionStringAsList(exclusionList));
- config.proxySettings = proxySettings;
- config.httpProxy = proxyInfo;
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyInfo);
break;
case PAC:
ProxyInfo proxyPacProperties =
ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
- config.proxySettings = proxySettings;
- config.httpProxy = proxyPacProperties;
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyPacProperties);
break;
case NONE:
- config.proxySettings = proxySettings;
+ config.setProxySettings(proxySettings);
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED proxy on file, use NONE");
- config.proxySettings = ProxySettings.NONE;
+ config.setProxySettings(ProxySettings.NONE);
break;
default:
loge("Ignore invalid proxy settings while reading");
- config.proxySettings = ProxySettings.UNASSIGNED;
+ config.setProxySettings(ProxySettings.UNASSIGNED);
break;
}
} else {
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..fc88f39 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -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/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index cb3b5c9..44ff3eb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -22,7 +22,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Binder;
import com.android.internal.util.CollectionUtils;
import com.android.server.compat.PlatformCompat;
@@ -77,9 +76,7 @@
static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,
long changeId) {
- //noinspection ConstantConditions
- return Binder.withCleanCallingIdentity(
- () -> platformCompat.isChangeEnabled(changeId, buildMockAppInfo(pkg)));
+ return platformCompat.isChangeEnabledInternalNoLogging(changeId, buildMockAppInfo(pkg));
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d0aa28b..1b5bb95 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2341,8 +2341,6 @@
params.packageName = packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- params.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index a7f3cdb..c029bf5 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.hardware.SensorPrivacyManager;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ResultReceiver;
@@ -79,6 +80,7 @@
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__FAILURE;
private final Context mContext;
+ private final SensorPrivacyManager mPrivacyManager;
boolean mIsServiceEnabled;
public RotationResolverManagerService(Context context) {
@@ -89,6 +91,7 @@
PACKAGE_UPDATE_POLICY_REFRESH_EAGER
| /*To avoid high rotation latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
mContext = context;
+ mPrivacyManager = SensorPrivacyManager.getInstance(context);
}
@Override
@@ -156,15 +159,22 @@
Objects.requireNonNull(callbackInternal);
Objects.requireNonNull(cancellationSignalInternal);
synchronized (mLock) {
- if (mIsServiceEnabled) {
- final RotationResolverManagerPerUserService service = getServiceForUserLocked(
- UserHandle.getCallingUserId());
+ final boolean isCameraAvailable = !mPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.Sensors.CAMERA);
+ if (mIsServiceEnabled && isCameraAvailable) {
+ final RotationResolverManagerPerUserService service =
+ getServiceForUserLocked(
+ UserHandle.getCallingUserId());
final RotationResolutionRequest request = new RotationResolutionRequest("",
currentRotation, proposedRotation, true, timeout);
service.resolveRotationLocked(callbackInternal, request,
cancellationSignalInternal);
} else {
- Slog.w(TAG, "Rotation Resolver service is disabled.");
+ if (isCameraAvailable) {
+ Slog.w(TAG, "Rotation Resolver service is disabled.");
+ } else {
+ Slog.w(TAG, "Camera is locked by a toggle.");
+ }
callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
logRotationStats(proposedRotation, currentRotation, RESOLUTION_DISABLED);
}
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..23b5d98 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 == null || getClass() != o.getClass()) {
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..7757a7a
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -0,0 +1,297 @@
+/*
+ * 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() {
+ return mServiceConnection.getBoundServiceInfo().toString();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("target service=" + mServiceConnection.getBoundServiceInfo());
+ pw.println("connected=" + mServiceConnection.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/timezone/TimeZoneUpdateIdler.java b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
index a7767a4..23e3eba 100644
--- a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
+++ b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
@@ -16,8 +16,6 @@
package com.android.server.timezone;
-import com.android.server.LocalServices;
-
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -26,6 +24,8 @@
import android.content.Context;
import android.util.Slog;
+import com.android.server.LocalServices;
+
/**
* A JobService used to trigger time zone rules update work when a device falls idle.
*/
@@ -55,7 +55,7 @@
@Override
public boolean onStopJob(JobParameters params) {
// Reschedule if stopped unless it was cancelled due to unschedule().
- boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+ boolean reschedule = params.getStopReason() != JobParameters.STOP_REASON_CANCELLED_BY_APP;
Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
return reschedule;
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index 4fa920e..f054c57 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -47,7 +47,7 @@
@NonNull String providerName,
@NonNull LocationTimeZoneProviderProxy proxy) {
super(providerMetricsLogger, threadingDomain, providerName,
- new ZoneInfoDbTimeZoneIdValidator());
+ new ZoneInfoDbTimeZoneProviderEventPreProcessor());
mProxy = Objects.requireNonNull(proxy);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index cc815dc6..e116a87 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -20,7 +20,6 @@
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
@@ -86,18 +85,6 @@
}
/**
- * Used by {@link LocationTimeZoneProvider} to check if time zone IDs are understood
- * by the platform.
- */
- interface TimeZoneIdValidator {
-
- /**
- * Returns whether {@code timeZoneId} is supported by the platform or not.
- */
- boolean isValid(@NonNull String timeZoneId);
- }
-
- /**
* Listener interface used to log provider events for metrics.
*/
interface ProviderMetricsLogger {
@@ -386,19 +373,20 @@
// Non-null and effectively final after initialize() is called.
ProviderListener mProviderListener;
- @NonNull private TimeZoneIdValidator mTimeZoneIdValidator;
+ @NonNull private final TimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
/** Creates the instance. */
LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
- @NonNull TimeZoneIdValidator timeZoneIdValidator) {
+ @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger);
mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
mSharedLock = threadingDomain.getLockObject();
mProviderName = Objects.requireNonNull(providerName);
- mTimeZoneIdValidator = Objects.requireNonNull(timeZoneIdValidator);
+ mTimeZoneProviderEventPreProcessor =
+ Objects.requireNonNull(timeZoneProviderEventPreProcessor);
}
/**
@@ -639,24 +627,8 @@
mThreadingDomain.assertCurrentThread();
Objects.requireNonNull(timeZoneProviderEvent);
- // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
- // the device's time zone. This logic prevents bad time zone IDs entering the time zone
- // detection logic from third party code.
- //
- // An event containing an unknown time zone ID could occur if the provider is using a
- // different TZDB version than the device. Provider developers are expected to take steps to
- // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
- // rules, or providing IDs based on the device's TZDB version, so this is not considered a
- // common case.
- //
- // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
- // enables immediate failover to a secondary provider, one that might provide valid IDs for
- // the same location, which should provide better behavior than just ignoring the event.
- if (hasInvalidTimeZones(timeZoneProviderEvent)) {
- infoLog("event=" + timeZoneProviderEvent + " has unsupported time zones. "
- + "Replacing it with uncertain event.");
- timeZoneProviderEvent = TimeZoneProviderEvent.createUncertainEvent();
- }
+ timeZoneProviderEvent =
+ mTimeZoneProviderEventPreProcessor.preProcess(timeZoneProviderEvent);
synchronized (mSharedLock) {
debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName
@@ -755,20 +727,6 @@
}
}
- private boolean hasInvalidTimeZones(@NonNull TimeZoneProviderEvent event) {
- if (event.getSuggestion() == null) {
- return false;
- }
-
- for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
- if (!mTimeZoneIdValidator.isValid(timeZone)) {
- return true;
- }
- }
-
- return false;
- }
-
@GuardedBy("mSharedLock")
private void assertIsStarted() {
ProviderState currentState = mCurrentState.get();
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/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
similarity index 64%
rename from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
rename to services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
index cab5ad2..951e9d0 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
@@ -18,13 +18,16 @@
import android.annotation.NonNull;
-import com.android.i18n.timezone.ZoneInfoDb;
+/**
+ * Used by {@link LocationTimeZoneProvider} to ensure that all time zone IDs are understood by the
+ * platform.
+ */
+public interface TimeZoneProviderEventPreProcessor {
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
+ /**
+ * May return uncertain event if {@code timeZoneProviderEvent} is ill-formed or drop/rewrite
+ * time zone IDs.
+ */
+ TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent timeZoneProviderEvent);
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..0f4367d
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -0,0 +1,72 @@
+/*
+ * 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.timezonedetector.location;
+
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
+
+import android.annotation.NonNull;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+/**
+ * {@link TimeZoneProviderEventPreProcessor} implementation which makes validations against
+ * {@link ZoneInfoDb}.
+ */
+public class ZoneInfoDbTimeZoneProviderEventPreProcessor
+ implements TimeZoneProviderEventPreProcessor {
+
+ /**
+ * Returns uncertain event if {@code event} has at least one unsupported time zone ID.
+ */
+ @Override
+ public TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent event) {
+ if (event.getSuggestion() == null || event.getSuggestion().getTimeZoneIds().isEmpty()) {
+ return event;
+ }
+
+ // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
+ // the device's time zone. This logic prevents bad time zone IDs entering the time zone
+ // detection logic from third party code.
+ //
+ // An event containing an unknown time zone ID could occur if the provider is using a
+ // different TZDB version than the device. Provider developers are expected to take steps to
+ // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
+ // rules, or providing IDs based on the device's TZDB version, so this is not considered a
+ // common case.
+ //
+ // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
+ // enables immediate failover to a secondary provider, one that might provide valid IDs for
+ // the same location, which should provide better behavior than just ignoring the event.
+ if (hasInvalidZones(event)) {
+ return TimeZoneProviderEvent.createUncertainEvent();
+ }
+
+ return event;
+ }
+
+ private static boolean hasInvalidZones(TimeZoneProviderEvent event) {
+ for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
+ if (!ZoneInfoDb.getInstance().hasTimeZone(timeZone)) {
+ infoLog("event=" + event + " has unsupported zone(" + timeZone + ")");
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e84ee672..1e897ea 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,17 +16,23 @@
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.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 +67,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 +120,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 +332,49 @@
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 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.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/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..18063de 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -28,6 +28,10 @@
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.WorkSource;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import android.util.SparseArray;
@@ -248,22 +252,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 +628,14 @@
}
private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+ 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 +643,31 @@
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));
@@ -713,33 +725,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 +760,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 +777,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 +812,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 +822,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);
}
}
@@ -909,13 +915,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..66200b3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -22,8 +22,9 @@
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.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -156,21 +157,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 +201,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 +213,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();
@@ -313,13 +312,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 +358,7 @@
}
/** Sets the amplitude for the vibrator to run. */
- public void setAmplitude(int amplitude) {
+ public void setAmplitude(float amplitude) {
setAmplitude(mNativePtr, amplitude);
}
@@ -379,9 +378,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/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index e6d37b6..b947c88 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1511,10 +1511,15 @@
IBinder topFocusedWindowToken = null;
synchronized (mService.mGlobalLock) {
- // Do not send the windows if there is no top focus as
- // the window manager is still looking for where to put it.
- // We will do the work when we get a focus change callback.
- final WindowState topFocusedWindowState = getTopFocusWindow();
+ // If there is a recents animation running, then use the animation target as the
+ // top window state. Otherwise,do not send the windows if there is no top focus as
+ // the window manager is still looking for where to put it. We will do the work when
+ // we get a focus change callback.
+ final RecentsAnimationController controller =
+ mService.getRecentsAnimationController();
+ final WindowState topFocusedWindowState = controller != null
+ ? controller.getTargetAppMainWindow()
+ : getTopFocusWindow();
if (topFocusedWindowState == null) {
if (DEBUG) {
Slog.d(LOG_TAG, "top focused window is null, compute it again later");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7c23661..eadfbe2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6790,7 +6790,12 @@
// The app bounds hasn't been computed yet.
return false;
}
- final Configuration parentConfig = getParent().getConfiguration();
+ final WindowContainer parent = getParent();
+ if (parent == null) {
+ // The parent of detached Activity can be null.
+ return false;
+ }
+ final Configuration parentConfig = parent.getConfiguration();
// Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
// fields should be changed with density and bounds, so here only compares the most
// significant field.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 46913eb..29c5cec 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -251,6 +251,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto;
import com.android.server.am.AppTimeTracker;
+import com.android.server.am.AssistDataRequester;
import com.android.server.am.BaseErrorDialog;
import com.android.server.am.PendingIntentController;
import com.android.server.am.PendingIntentRecord;
@@ -2828,10 +2829,45 @@
@Override
public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
- Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
+ Bundle receiverExtras, IBinder activityToken, boolean checkActivityIsTop,
+ boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
- activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
- PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+ activityToken, checkActivityIsTop, newSessionId, UserHandle.getCallingUserId(),
+ null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+ }
+
+ @Override
+ public boolean requestAssistDataForTask(IAssistDataReceiver receiver, int taskId,
+ String callingPackageName) {
+ mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
+ "requestAssistDataForTask()");
+ final long callingId = Binder.clearCallingIdentity();
+ LocalService.ActivityTokens tokens = null;
+ try {
+ tokens = mInternal.getTopActivityForTask(taskId);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ if (tokens == null) {
+ Log.e(TAG, "Could not find activity for task " + taskId);
+ return false;
+ }
+
+ final AssistDataReceiverProxy proxy =
+ new AssistDataReceiverProxy(receiver, callingPackageName);
+ Object lock = new Object();
+ AssistDataRequester requester = new AssistDataRequester(mContext, mWindowManager,
+ getAppOpsManager(), proxy, lock, AppOpsManager.OP_ASSIST_STRUCTURE,
+ AppOpsManager.OP_NONE);
+
+ List<IBinder> topActivityToken = new ArrayList<>();
+ topActivityToken.add(tokens.getActivityToken());
+ requester.requestAssistData(topActivityToken, true /* fetchData */,
+ false /* fetchScreenshot */, true /* allowFetchData */,
+ false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */,
+ Binder.getCallingUid(), callingPackageName);
+
+ return true;
}
@Override
@@ -2845,7 +2881,7 @@
@Override
public Bundle getAssistContextExtras(int requestType) {
PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
- null, null, true /* focused */, true /* newSessionId */,
+ null, null, true /* checkActivityIsTop */, true /* newSessionId */,
UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
if (pae == null) {
return null;
@@ -3048,8 +3084,8 @@
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
- boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
- int flags) {
+ boolean checkActivityIsTop, boolean newSessionId, int userHandle, Bundle args,
+ long timeout, int flags) {
mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"enqueueAssistContext()");
@@ -3065,7 +3101,7 @@
Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity);
return null;
}
- if (focused) {
+ if (checkActivityIsTop) {
if (activityToken != null) {
ActivityRecord caller = ActivityRecord.forTokenLocked(activityToken);
if (activity != caller) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6d24105..6072a06 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -516,7 +516,7 @@
/** The delay to avoid toggling the animation quickly. */
private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
- private FixedRotationAnimationController mFixedRotationAnimationController;
+ private FadeRotationAnimationController mFadeRotationAnimationController;
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1590,8 +1590,8 @@
}
@VisibleForTesting
- @Nullable FixedRotationAnimationController getFixedRotationAnimationController() {
- return mFixedRotationAnimationController;
+ @Nullable FadeRotationAnimationController getFadeRotationAnimationController() {
+ return mFadeRotationAnimationController;
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1601,13 +1601,13 @@
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
if (mFixedRotationLaunchingApp == null && r != null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
- startFixedRotationAnimation(
+ startFadeRotationAnimation(
// Delay the hide animation to avoid blinking by clicking navigation bar that
// may toggle fixed rotation in a short time.
r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
- finishFixedRotationAnimationIfPossible();
+ finishFadeRotationAnimationIfPossible();
}
mFixedRotationLaunchingApp = r;
}
@@ -1714,12 +1714,12 @@
*
* @return {@code true} if the animation is executed right now.
*/
- private boolean startFixedRotationAnimation(boolean shouldDebounce) {
+ private boolean startFadeRotationAnimation(boolean shouldDebounce) {
if (shouldDebounce) {
mWmService.mH.postDelayed(() -> {
synchronized (mWmService.mGlobalLock) {
if (mFixedRotationLaunchingApp != null
- && startFixedRotationAnimation(false /* shouldDebounce */)) {
+ && startFadeRotationAnimation(false /* shouldDebounce */)) {
// Apply the transaction so the animation leash can take effect immediately.
getPendingTransaction().apply();
}
@@ -1727,23 +1727,41 @@
}, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
return false;
}
- if (mFixedRotationAnimationController == null) {
- mFixedRotationAnimationController = new FixedRotationAnimationController(this);
- mFixedRotationAnimationController.hide();
+ if (mFadeRotationAnimationController == null) {
+ mFadeRotationAnimationController = new FadeRotationAnimationController(this);
+ mFadeRotationAnimationController.hide();
return true;
}
return false;
}
/** Re-show the previously hidden windows if all seamless rotated windows are done. */
- void finishFixedRotationAnimationIfPossible() {
- final FixedRotationAnimationController controller = mFixedRotationAnimationController;
+ void finishFadeRotationAnimationIfPossible() {
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
controller.show();
- mFixedRotationAnimationController = null;
+ mFadeRotationAnimationController = null;
}
}
+ /** Shows the given window which may be hidden for screen frozen. */
+ void finishFadeRotationAnimation(WindowState w) {
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ if (controller != null && controller.show(w.mToken)) {
+ mFadeRotationAnimationController = null;
+ }
+ }
+
+ /** Returns {@code true} if the display should wait for the given window to stop freezing. */
+ boolean waitForUnfreeze(WindowState w) {
+ if (w.mForceSeamlesslyRotate) {
+ // The window should look no different before and after rotation.
+ return false;
+ }
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ return controller == null || !controller.isTargetToken(w.mToken);
+ }
+
void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
if (mFixedRotationLaunchingApp != null) {
// The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2964,6 +2982,13 @@
mScreenRotationAnimation.kill();
}
mScreenRotationAnimation = screenRotationAnimation;
+
+ // Hide the windows which are not significant in rotation animation. So that the windows
+ // don't need to block the unfreeze time.
+ if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()
+ && mFadeRotationAnimationController == null) {
+ startFadeRotationAnimation(false /* shouldDebounce */);
+ }
}
public ScreenRotationAnimation getRotationAnimation() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d0e4c40..6046cc6 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -626,7 +626,7 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimationIfPossible();
+ mDisplayContent.finishFadeRotationAnimationIfPossible();
}
private void prepareSeamlessRotation() {
@@ -717,7 +717,7 @@
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimationIfPossible();
+ mDisplayContent.finishFadeRotationAnimationIfPossible();
updateRotationAndSendNewConfigIfChanged();
}
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
new file mode 100644
index 0000000..5ee6928
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -0,0 +1,135 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * Controller to fade out and in windows when the display is changing rotation. It can be used for
+ * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
+ * the windows until they are drawn with the new rotation.
+ */
+public class FadeRotationAnimationController extends FadeAnimationController {
+
+ private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
+ private final WindowManagerService mService;
+ /** If non-null, it usually indicates that there will be a screen rotation animation. */
+ private final Runnable mFrozenTimeoutRunnable;
+ private final WindowToken mNavBarToken;
+
+ public FadeRotationAnimationController(DisplayContent displayContent) {
+ super(displayContent);
+ mService = displayContent.mWmService;
+ mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
+ synchronized (mService.mGlobalLock) {
+ displayContent.finishFadeRotationAnimationIfPossible();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+ } : null;
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ final WindowState navigationBar = displayPolicy.getNavigationBar();
+ if (navigationBar != null) {
+ mNavBarToken = navigationBar.mToken;
+ final RecentsAnimationController controller = mService.getRecentsAnimationController();
+ final boolean navBarControlledByRecents =
+ controller != null && controller.isNavigationBarAttachedToApp();
+ // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
+ // bar is currently controlled by recents animation.
+ if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
+ mTargetWindowTokens.add(mNavBarToken);
+ }
+ } else {
+ mNavBarToken = null;
+ }
+ displayContent.forAllWindows(w -> {
+ if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
+ && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar) {
+ mTargetWindowTokens.add(w.mToken);
+ }
+ }, true /* traverseTopToBottom */);
+ }
+
+ /** Applies show animation on the previously hidden window tokens. */
+ void show() {
+ for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+ final WindowToken windowToken = mTargetWindowTokens.get(i);
+ fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+ }
+ mTargetWindowTokens.clear();
+ if (mFrozenTimeoutRunnable != null) {
+ mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ }
+ }
+
+ /**
+ * Returns {@code true} if all target windows are shown. It only takes effects if this
+ * controller is created for normal rotation.
+ */
+ boolean show(WindowToken token) {
+ if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
+ fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
+ if (mTargetWindowTokens.isEmpty()) {
+ mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
+ void hide() {
+ for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+ final WindowToken windowToken = mTargetWindowTokens.get(i);
+ fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+ }
+ if (mFrozenTimeoutRunnable != null) {
+ mService.mH.postDelayed(mFrozenTimeoutRunnable,
+ WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
+ }
+ }
+
+ /** Returns {@code true} if the window is handled by this controller. */
+ boolean isTargetToken(WindowToken token) {
+ return token == mNavBarToken || mTargetWindowTokens.contains(token);
+ }
+
+ @Override
+ public Animation getFadeInAnimation() {
+ if (mFrozenTimeoutRunnable != null) {
+ // Use a shorter animation so it is easier to align with screen rotation animation.
+ return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
+ }
+ return super.getFadeInAnimation();
+ }
+
+ @Override
+ public Animation getFadeOutAnimation() {
+ if (mFrozenTimeoutRunnable != null) {
+ // Hide the window immediately because screen should have been covered by screenshot.
+ return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
+ }
+ return super.getFadeOutAnimation();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
deleted file mode 100644
index aa73170..0000000
--- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.wm;
-
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
-
-import java.util.ArrayList;
-
-/**
- * Controller to fade out and in system ui when applying a fixed rotation transform to a window
- * token.
- *
- * The system bars will be fade out when the fixed rotation transform starts and will be fade in
- * once all surfaces have been rotated.
- */
-public class FixedRotationAnimationController extends FadeAnimationController {
-
- private final WindowState mStatusBar;
- private final WindowState mNavigationBar;
- private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2);
-
- public FixedRotationAnimationController(DisplayContent displayContent) {
- super(displayContent);
- final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
- mStatusBar = displayPolicy.getStatusBar();
-
- final RecentsAnimationController controller =
- displayContent.mWmService.getRecentsAnimationController();
- final boolean navBarControlledByRecents =
- controller != null && controller.isNavigationBarAttachedToApp();
- // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation bar
- // is currently controlled by recents animation.
- mNavigationBar = !displayPolicy.navigationBarCanMove()
- && !navBarControlledByRecents ? displayPolicy.getNavigationBar() : null;
- }
-
- /** Applies show animation on the previously hidden window tokens. */
- void show() {
- for (int i = mAnimatedWindowToken.size() - 1; i >= 0; i--) {
- final WindowToken windowToken = mAnimatedWindowToken.get(i);
- fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- }
-
- /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
- void hide() {
- if (mNavigationBar != null) {
- fadeWindowToken(false /* show */, mNavigationBar.mToken,
- ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- if (mStatusBar != null) {
- fadeWindowToken(false /* show */, mStatusBar.mToken,
- ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- }
-
- @Override
- public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
- super.fadeWindowToken(show, windowToken, animationType);
- mAnimatedWindowToken.add(windowToken);
- }
-}
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/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 4ab5cd6..b1e12b6 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -77,7 +77,7 @@
final boolean shouldAttachNavBarToApp =
displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
&& service.getRecentsAnimationController() == null
- && displayContent.getFixedRotationAnimationController() == null;
+ && displayContent.getFadeRotationAnimationController() == null;
if (shouldAttachNavBarToApp) {
startNavigationBarWindowAnimation(
displayContent, durationHint, statusBarTransitionDelay, targets,
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 64ff108..bd84854 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -568,8 +568,9 @@
? mMinimizedHomeBounds
: null;
final Rect contentInsets;
- if (mTargetActivityRecord != null && mTargetActivityRecord.findMainWindow() != null) {
- contentInsets = mTargetActivityRecord.findMainWindow()
+ final WindowState targetAppMainWindow = getTargetAppMainWindow();
+ if (targetAppMainWindow != null) {
+ contentInsets = targetAppMainWindow
.getInsetsStateWithVisibilityOverride()
.calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(),
false /* ignoreVisibility */);
@@ -607,8 +608,8 @@
private void attachNavigationBarToApp() {
if (!mShouldAttachNavBarToAppDuringTransition
- // Skip the case where the nav bar is controlled by fixed rotation.
- || mDisplayContent.getFixedRotationAnimationController() != null) {
+ // Skip the case where the nav bar is controlled by fade rotation.
+ || mDisplayContent.getFadeRotationAnimationController() != null) {
return;
}
ActivityRecord topActivity = null;
@@ -1004,9 +1005,7 @@
boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle) {
// Update the input consumer touchable region to match the target app main window
- final WindowState targetAppMainWindow = mTargetActivityRecord != null
- ? mTargetActivityRecord.findMainWindow()
- : null;
+ final WindowState targetAppMainWindow = getTargetAppMainWindow();
if (targetAppMainWindow != null) {
targetAppMainWindow.getBounds(mTmpRect);
inputWindowHandle.touchableRegion.set(mTmpRect);
@@ -1026,6 +1025,13 @@
return mTargetActivityRecord.windowsCanBeWallpaperTarget();
}
+ WindowState getTargetAppMainWindow() {
+ if (mTargetActivityRecord == null) {
+ return null;
+ }
+ return mTargetActivityRecord.findMainWindow();
+ }
+
boolean isAnimatingTask(Task task) {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
if (task == mPendingAnimations.get(i).mTask) {
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 57394d6..6b1071c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -433,11 +433,6 @@
public static boolean sEnableRemoteKeyguardAnimation =
SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
- private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY =
- "ro.sf.disable_triple_buffer";
-
- static boolean sEnableTripleBuffering = !SystemProperties.getBoolean(
- DISABLE_TRIPLE_BUFFERING_PROPERTY, false);
/**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -1747,9 +1742,6 @@
if (mUseBLAST) {
res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
}
- if (sEnableTripleBuffering) {
- res |= WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING;
- }
if (displayContent.mCurrentFocus == null) {
displayContent.mWinAddedSinceNullFocus.add(win);
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 5ef9420..49e704f 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -1150,7 +1150,8 @@
FrameworkStatsLog.write(
FrameworkStatsLog.DEVICE_ROTATED,
event.timestamp,
- rotationToLogEnum(reportedRotation));
+ rotationToLogEnum(reportedRotation),
+ FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d88387..6da350b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1514,11 +1514,20 @@
}
void setOrientationChanging(boolean changing) {
- mOrientationChanging = changing;
mOrientationChangeTimedOut = false;
+ if (mOrientationChanging == changing) {
+ return;
+ }
+ mOrientationChanging = changing;
if (changing) {
mLastFreezeDuration = 0;
- mWmService.mRoot.mOrientationChangeComplete = false;
+ if (mWmService.mRoot.mOrientationChangeComplete
+ && mDisplayContent.waitForUnfreeze(this)) {
+ mWmService.mRoot.mOrientationChangeComplete = false;
+ }
+ } else {
+ // The orientation change is completed. If it was hidden by the animation, reshow it.
+ mDisplayContent.finishFadeRotationAnimation(this);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ebbebbb..0c80f86 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -656,8 +656,10 @@
if (w.getOrientationChanging()) {
if (!w.isDrawn()) {
- w.mWmService.mRoot.mOrientationChangeComplete = false;
- mAnimator.mLastWindowFreezeSource = w;
+ if (w.mDisplayContent.waitForUnfreeze(w)) {
+ w.mWmService.mRoot.mOrientationChangeComplete = false;
+ mAnimator.mLastWindowFreezeSource = w;
+ }
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation continue waiting for draw in %s", w);
} else {
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..ca56e9b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3092,7 +3092,8 @@
boolean enableEnterpriseNetworkSlice = true;
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
- enableEnterpriseNetworkSlice = owner != null ? owner.mNetworkSlicingEnabled : true;
+ enableEnterpriseNetworkSlice = owner != null
+ ? owner.mEnterpriseNetworkPreferenceEnabled : true;
}
updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkSlice);
@@ -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;
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a88f2b4..932e997 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -377,6 +377,7 @@
dprintf(fd, "Mounts (%d): {\n", int(mMounts.size()));
for (auto&& [id, ifs] : mMounts) {
+ std::unique_lock ll(ifs->lock);
const IncFsMount& mnt = *ifs;
dprintf(fd, " [%d]: {\n", id);
if (id != mnt.mountId) {
@@ -422,6 +423,9 @@
}
bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+ if (!ifs.dataLoaderStub) {
+ return false;
+ }
if (ifs.dataLoaderStub->isSystemDataLoader()) {
return true;
}
@@ -439,6 +443,8 @@
std::lock_guard l(mLock);
mounts.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
+ std::unique_lock ll(ifs->lock);
+
if (ifs->mountId != id) {
continue;
}
@@ -456,7 +462,10 @@
std::thread([this, mounts = std::move(mounts)]() {
mJni->initializeForCurrentThread();
for (auto&& ifs : mounts) {
- ifs->dataLoaderStub->requestStart();
+ std::unique_lock l(ifs->lock);
+ if (ifs->dataLoaderStub) {
+ ifs->dataLoaderStub->requestStart();
+ }
}
}).detach();
}
@@ -671,23 +680,36 @@
setUidReadTimeouts(storageId, std::move(perUidReadTimeouts));
}
- // Re-initialize DataLoader.
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
- if (!ifs) {
- return false;
- }
- if (ifs->dataLoaderStub) {
- ifs->dataLoaderStub->cleanupResources();
- ifs->dataLoaderStub = {};
- }
- l.unlock();
+ IfsMountPtr ifs;
+ DataLoaderStubPtr dataLoaderStub;
- // DataLoader.
- auto dataLoaderStub =
- prepareDataLoader(*ifs, std::move(dataLoaderParams), std::move(statusListener),
- healthCheckParams, std::move(healthListener));
- CHECK(dataLoaderStub);
+ // Re-initialize DataLoader.
+ {
+ ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
+
+ std::unique_lock l(ifs->lock);
+ dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+ }
+
+ if (dataLoaderStub) {
+ dataLoaderStub->cleanupResources();
+ dataLoaderStub = {};
+ }
+
+ {
+ std::unique_lock l(ifs->lock);
+ if (ifs->dataLoaderStub) {
+ LOG(INFO) << "Skipped data loader stub creation because it already exists";
+ return false;
+ }
+ prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams), std::move(statusListener),
+ healthCheckParams, std::move(healthListener));
+ CHECK(ifs->dataLoaderStub);
+ dataLoaderStub = ifs->dataLoaderStub;
+ }
if (dataLoaderStub->isSystemDataLoader()) {
// Readlogs from system dataloader (adb) can always be collected.
@@ -705,13 +727,14 @@
<< storageId;
return;
}
+ std::unique_lock l(ifs->lock);
if (ifs->startLoadingTs != startLoadingTs) {
LOG(INFO) << "Can't disable the readlogs, timestamp mismatch (new "
"installation?): "
<< storageId;
return;
}
- setStorageParams(*ifs, storageId, /*enableReadLogs=*/false);
+ disableReadLogsLocked(*ifs);
});
}
@@ -733,17 +756,17 @@
}
void IncrementalService::disallowReadLogs(StorageId storageId) {
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
+ const auto ifs = getIfs(storageId);
if (!ifs) {
LOG(ERROR) << "disallowReadLogs failed, invalid storageId: " << storageId;
return;
}
+
+ std::unique_lock l(ifs->lock);
if (!ifs->readLogsAllowed()) {
return;
}
ifs->disallowReadLogs();
- l.unlock();
const auto metadata = constants().readLogsDisabledMarkerName;
if (auto err = mIncFs->makeFile(ifs->control,
@@ -755,7 +778,7 @@
return;
}
- setStorageParams(storageId, /*enableReadLogs=*/false);
+ disableReadLogsLocked(*ifs);
}
int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
@@ -764,61 +787,66 @@
LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
return -EINVAL;
}
- return setStorageParams(*ifs, storageId, enableReadLogs);
-}
-int IncrementalService::setStorageParams(IncFsMount& ifs, StorageId storageId,
- bool enableReadLogs) {
- const auto& params = ifs.dataLoaderStub->params();
- if (enableReadLogs) {
- if (!ifs.readLogsAllowed()) {
- LOG(ERROR) << "setStorageParams failed, readlogs disallowed for storageId: "
- << storageId;
- return -EPERM;
- }
-
- // Check loader usage stats permission and apop.
- if (auto status = mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage,
- params.packageName.c_str());
- !status.isOk()) {
- LOG(ERROR) << " Permission: " << kLoaderUsageStats
- << " check failed: " << status.toString8();
- return fromBinderStatus(status);
- }
-
- // Check multiuser permission.
- if (auto status = mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr,
- params.packageName.c_str());
- !status.isOk()) {
- LOG(ERROR) << " Permission: " << kInteractAcrossUsers
- << " check failed: " << status.toString8();
- return fromBinderStatus(status);
- }
-
- // Check installation time.
- const auto now = mClock->now();
- const auto startLoadingTs = ifs.startLoadingTs;
- if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
- LOG(ERROR) << "setStorageParams failed, readlogs can't be enabled at this time, "
- "storageId: "
- << storageId;
- return -EPERM;
- }
+ std::unique_lock l(ifs->lock);
+ if (!enableReadLogs) {
+ return disableReadLogsLocked(*ifs);
}
- if (auto status = applyStorageParams(ifs, enableReadLogs); !status.isOk()) {
- LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
+ if (!ifs->readLogsAllowed()) {
+ LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
+ return -EPERM;
+ }
+
+ if (!ifs->dataLoaderStub) {
+ // This should never happen - only DL can call enableReadLogs.
+ LOG(ERROR) << "enableReadLogs failed: invalid state";
+ return -EPERM;
+ }
+
+ // Check installation time.
+ const auto now = mClock->now();
+ const auto startLoadingTs = ifs->startLoadingTs;
+ if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
+ LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
+ << storageId;
+ return -EPERM;
+ }
+
+ const auto& packageName = ifs->dataLoaderStub->params().packageName;
+
+ // Check loader usage stats permission and apop.
+ if (auto status =
+ mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage, packageName.c_str());
+ !status.isOk()) {
+ LOG(ERROR) << " Permission: " << kLoaderUsageStats
+ << " check failed: " << status.toString8();
return fromBinderStatus(status);
}
- if (enableReadLogs) {
- registerAppOpsCallback(params.packageName);
+ // Check multiuser permission.
+ if (auto status =
+ mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr, packageName.c_str());
+ !status.isOk()) {
+ LOG(ERROR) << " Permission: " << kInteractAcrossUsers
+ << " check failed: " << status.toString8();
+ return fromBinderStatus(status);
}
+ if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) {
+ return status;
+ }
+
+ registerAppOpsCallback(packageName);
+
return 0;
}
-binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enableReadLogs) {
+int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) {
+ return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false);
+}
+
+int IncrementalService::applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs) {
os::incremental::IncrementalFileSystemControlParcel control;
control.cmd.reset(dup(ifs.control.cmd()));
control.pendingReads.reset(dup(ifs.control.pendingReads()));
@@ -832,8 +860,10 @@
if (status.isOk()) {
// Store enabled state.
ifs.setReadLogsEnabled(enableReadLogs);
+ } else {
+ LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
}
- return status;
+ return status.isOk() ? 0 : fromBinderStatus(status);
}
void IncrementalService::deleteStorage(StorageId storageId) {
@@ -1224,9 +1254,14 @@
return;
}
- const auto timeout = std::chrono::duration_cast<milliseconds>(maxPendingTimeUs) -
- Constants::perUidTimeoutOffset;
- updateUidReadTimeouts(storage, Clock::now() + timeout);
+ const auto timeout = Clock::now() + maxPendingTimeUs - Constants::perUidTimeoutOffset;
+ addIfsStateCallback(storage, [this, timeout](StorageId storageId, IfsState state) -> bool {
+ if (checkUidReadTimeouts(storageId, state, timeout)) {
+ return true;
+ }
+ clearUidReadTimeouts(storageId);
+ return false;
+ });
}
void IncrementalService::clearUidReadTimeouts(StorageId storage) {
@@ -1234,39 +1269,32 @@
if (!ifs) {
return;
}
-
mIncFs->setUidReadTimeouts(ifs->control, {});
}
-void IncrementalService::updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit) {
- // Reached maximum timeout.
+bool IncrementalService::checkUidReadTimeouts(StorageId storage, IfsState state,
+ Clock::time_point timeLimit) {
if (Clock::now() >= timeLimit) {
- return clearUidReadTimeouts(storage);
+ // Reached maximum timeout.
+ return false;
+ }
+ if (state.error) {
+ // Something is wrong, abort.
+ return false;
}
// Still loading?
- const auto state = isMountFullyLoaded(storage);
- if (int(state) < 0) {
- // Something is wrong, abort.
- return clearUidReadTimeouts(storage);
- }
-
- if (state == incfs::LoadingState::Full) {
- // Fully loaded, check readLogs collection.
- const auto ifs = getIfs(storage);
- if (!ifs->readLogsEnabled()) {
- return clearUidReadTimeouts(storage);
- }
+ if (state.fullyLoaded && !state.readLogsEnabled) {
+ return false;
}
const auto timeLeft = timeLimit - Clock::now();
if (timeLeft < Constants::progressUpdateInterval) {
// Don't bother.
- return clearUidReadTimeouts(storage);
+ return false;
}
- addTimedJob(*mTimedQueue, storage, Constants::progressUpdateInterval,
- [this, storage, timeLimit]() { updateUidReadTimeouts(storage, timeLimit); });
+ return true;
}
std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() {
@@ -1533,7 +1561,7 @@
dataLoaderParams.arguments = loader.arguments();
}
- prepareDataLoader(*ifs, std::move(dataLoaderParams));
+ prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams));
CHECK(ifs->dataLoaderStub);
std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
@@ -1615,24 +1643,10 @@
}
}
-IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
- IncFsMount& ifs, DataLoaderParamsParcel&& params, DataLoaderStatusListener&& statusListener,
- const StorageHealthCheckParams& healthCheckParams, StorageHealthListener&& healthListener) {
- std::unique_lock l(ifs.lock);
- prepareDataLoaderLocked(ifs, std::move(params), std::move(statusListener), healthCheckParams,
- std::move(healthListener));
- return ifs.dataLoaderStub;
-}
-
void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
DataLoaderStatusListener&& statusListener,
const StorageHealthCheckParams& healthCheckParams,
StorageHealthListener&& healthListener) {
- if (ifs.dataLoaderStub) {
- LOG(INFO) << "Skipped data loader preparation because it already exists";
- return;
- }
-
FileSystemControlParcel fsControlParcel;
fsControlParcel.incremental = std::make_optional<IncrementalFileSystemControlParcel>();
fsControlParcel.incremental->cmd.reset(dup(ifs.control.cmd()));
@@ -1647,6 +1661,29 @@
new DataLoaderStub(*this, ifs.mountId, std::move(params), std::move(fsControlParcel),
std::move(statusListener), healthCheckParams,
std::move(healthListener), path::join(ifs.root, constants().mount));
+
+ addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
+ if (!state.fullyLoaded || state.readLogsEnabled) {
+ return true;
+ }
+
+ DataLoaderStubPtr dataLoaderStub;
+ {
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
+
+ std::unique_lock l(ifs->lock);
+ dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+ }
+
+ if (dataLoaderStub) {
+ dataLoaderStub->cleanupResources();
+ }
+
+ return false;
+ });
}
template <class Duration>
@@ -2070,11 +2107,11 @@
StorageHealthListener healthListener) {
DataLoaderStubPtr dataLoaderStub;
{
- std::unique_lock l(mLock);
- const auto& ifs = getIfsLocked(storage);
+ const auto& ifs = getIfs(storage);
if (!ifs) {
return false;
}
+ std::unique_lock l(ifs->lock);
dataLoaderStub = ifs->dataLoaderStub;
if (!dataLoaderStub) {
return false;
@@ -2160,13 +2197,16 @@
std::lock_guard l(mLock);
affected.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
- if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) {
+ std::unique_lock ll(ifs->lock);
+
+ if (ifs->mountId == id && ifs->dataLoaderStub &&
+ ifs->dataLoaderStub->params().packageName == packageName) {
affected.push_back(ifs);
}
}
}
for (auto&& ifs : affected) {
- applyStorageParams(*ifs, false);
+ applyStorageParamsLocked(*ifs, /*enableReadLogs=*/false);
}
}
@@ -2187,6 +2227,101 @@
return true;
}
+void IncrementalService::addIfsStateCallback(StorageId storageId, IfsStateCallback callback) {
+ bool wasEmpty;
+ {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ wasEmpty = mIfsStateCallbacks.empty();
+ mIfsStateCallbacks[storageId].emplace_back(std::move(callback));
+ }
+ if (wasEmpty) {
+ addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ [this]() { processIfsStateCallbacks(); });
+ }
+}
+
+void IncrementalService::processIfsStateCallbacks() {
+ StorageId storageId = kInvalidStorageId;
+ std::vector<IfsStateCallback> local;
+ while (true) {
+ {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ if (mIfsStateCallbacks.empty()) {
+ return;
+ }
+ IfsStateCallbacks::iterator it;
+ if (storageId == kInvalidStorageId) {
+ // First entry, initialize the it.
+ it = mIfsStateCallbacks.begin();
+ } else {
+ // Subsequent entries, update the storageId, and shift to the new one.
+ it = mIfsStateCallbacks.find(storageId);
+ if (it == mIfsStateCallbacks.end()) {
+ // Was removed while processing, too bad.
+ break;
+ }
+
+ auto& callbacks = it->second;
+ if (callbacks.empty()) {
+ std::swap(callbacks, local);
+ } else {
+ callbacks.insert(callbacks.end(), local.begin(), local.end());
+ }
+ if (callbacks.empty()) {
+ it = mIfsStateCallbacks.erase(it);
+ if (mIfsStateCallbacks.empty()) {
+ return;
+ }
+ } else {
+ ++it;
+ }
+ }
+
+ if (it == mIfsStateCallbacks.end()) {
+ break;
+ }
+
+ storageId = it->first;
+ auto& callbacks = it->second;
+ if (callbacks.empty()) {
+ // Invalid case, one extra lookup should be ok.
+ continue;
+ }
+ std::swap(callbacks, local);
+ }
+
+ processIfsStateCallbacks(storageId, local);
+ }
+
+ addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ [this]() { processIfsStateCallbacks(); });
+}
+
+void IncrementalService::processIfsStateCallbacks(StorageId storageId,
+ std::vector<IfsStateCallback>& callbacks) {
+ const auto state = isMountFullyLoaded(storageId);
+ IfsState storageState = {};
+ storageState.error = int(state) < 0;
+ storageState.fullyLoaded = state == incfs::LoadingState::Full;
+ if (storageState.fullyLoaded) {
+ const auto ifs = getIfs(storageId);
+ storageState.readLogsEnabled = ifs && ifs->readLogsEnabled();
+ }
+
+ for (auto cur = callbacks.begin(); cur != callbacks.end();) {
+ if ((*cur)(storageId, storageState)) {
+ ++cur;
+ } else {
+ cur = callbacks.erase(cur);
+ }
+ }
+}
+
+void IncrementalService::removeIfsStateCallbacks(StorageId storageId) {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ mIfsStateCallbacks.erase(storageId);
+}
+
void IncrementalService::getMetrics(StorageId storageId, android::os::PersistableBundle* result) {
const auto duration = getMillsSinceOldestPendingRead(storageId);
if (duration >= 0) {
@@ -2197,12 +2332,12 @@
}
long IncrementalService::getMillsSinceOldestPendingRead(StorageId storageId) {
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
+ const auto ifs = getIfs(storageId);
if (!ifs) {
LOG(ERROR) << "getMillsSinceOldestPendingRead failed, invalid storageId: " << storageId;
return -EINVAL;
}
+ std::unique_lock l(ifs->lock);
if (!ifs->dataLoaderStub) {
LOG(ERROR) << "getMillsSinceOldestPendingRead failed, no data loader: " << storageId;
return -EINVAL;
@@ -2248,6 +2383,7 @@
resetHealthControl();
mService.removeTimedJobs(*mService.mTimedQueue, mId);
}
+ mService.removeIfsStateCallbacks(mId);
requestDestroy();
@@ -2758,7 +2894,7 @@
mService.mLooper->addFd(
pendingReadsFd, android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
[](int, int, void* data) -> int {
- auto&& self = (DataLoaderStub*)data;
+ auto self = (DataLoaderStub*)data;
self->updateHealthStatus(/*baseline=*/true);
return 0;
},
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 4a5db06..a8f32de 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -74,6 +74,17 @@
using PerUidReadTimeouts = ::android::os::incremental::PerUidReadTimeouts;
+struct IfsState {
+ // If mount is fully loaded.
+ bool fullyLoaded = false;
+ // If read logs are enabled on this mount. Populated only if fullyLoaded == true.
+ bool readLogsEnabled = false;
+ // If there was an error fetching any of the above.
+ bool error = false;
+};
+// Returns true if wants to be called again.
+using IfsStateCallback = std::function<bool(StorageId, IfsState)>;
+
class IncrementalService final {
public:
explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir);
@@ -366,7 +377,7 @@
void setUidReadTimeouts(StorageId storage,
std::vector<PerUidReadTimeouts>&& perUidReadTimeouts);
void clearUidReadTimeouts(StorageId storage);
- void updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit);
+ bool checkUidReadTimeouts(StorageId storage, IfsState state, Clock::time_point timeLimit);
std::unordered_set<std::string_view> adoptMountedInstances();
void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames);
@@ -387,11 +398,6 @@
bool needStartDataLoaderLocked(IncFsMount& ifs);
- DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
- content::pm::DataLoaderParamsParcel&& params,
- DataLoaderStatusListener&& statusListener = {},
- const StorageHealthCheckParams& healthCheckParams = {},
- StorageHealthListener&& healthListener = {});
void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params,
DataLoaderStatusListener&& statusListener = {},
const StorageHealthCheckParams& healthCheckParams = {},
@@ -410,8 +416,8 @@
std::string_view path) const;
int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode);
- int setStorageParams(IncFsMount& ifs, StorageId storageId, bool enableReadLogs);
- binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
+ int disableReadLogsLocked(IncFsMount& ifs);
+ int applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs);
LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
@@ -431,6 +437,12 @@
bool addTimedJob(TimedQueueWrapper& timedQueue, MountId id, Milliseconds after, Job what);
bool removeTimedJobs(TimedQueueWrapper& timedQueue, MountId id);
+
+ void addIfsStateCallback(StorageId storageId, IfsStateCallback callback);
+ void removeIfsStateCallbacks(StorageId storageId);
+ void processIfsStateCallbacks();
+ void processIfsStateCallbacks(StorageId storageId, std::vector<IfsStateCallback>& callbacks);
+
bool updateLoadingProgress(int32_t storageId,
StorageLoadingProgressListener&& progressListener);
long getMillsSinceOldestPendingRead(StorageId storage);
@@ -456,6 +468,10 @@
std::mutex mCallbacksLock;
std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered;
+ using IfsStateCallbacks = std::unordered_map<StorageId, std::vector<IfsStateCallback>>;
+ std::mutex mIfsStateCallbacksLock;
+ IfsStateCallbacks mIfsStateCallbacks;
+
std::atomic_bool mSystemReady = false;
StorageId mNextId = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 54bc95d..de8822d 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -140,9 +140,7 @@
const content::pm::FileSystemControlParcel& control,
const sp<content::pm::IDataLoaderStatusListener>& listener) {
createOkNoStatus(id, params, control, listener);
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_CREATED);
- }
+ reportStatus(id);
return binder::Status::ok();
}
binder::Status createOkNoStatus(int32_t id, const content::pm::DataLoaderParamsParcel& params,
@@ -150,33 +148,26 @@
const sp<content::pm::IDataLoaderStatusListener>& listener) {
mServiceConnector = control.service;
mListener = listener;
+ mStatus = IDataLoaderStatusListener::DATA_LOADER_CREATED;
return binder::Status::ok();
}
binder::Status startOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
return binder::Status::ok();
}
binder::Status stopOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
return binder::Status::ok();
}
binder::Status destroyOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
mListener = nullptr;
return binder::Status::ok();
}
binder::Status prepareImageOk(int32_t id,
const ::std::vector<content::pm::InstallationFileParcel>&,
const ::std::vector<::std::string>&) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
return binder::Status::ok();
}
binder::Status storageError(int32_t id) {
@@ -197,10 +188,22 @@
EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk());
return result;
}
+ int status() const { return mStatus; }
private:
+ void setAndReportStatus(int id, int status) {
+ mStatus = status;
+ reportStatus(id);
+ }
+ void reportStatus(int id) {
+ if (mListener) {
+ mListener->onStatusChanged(id, mStatus);
+ }
+ }
+
sp<IIncrementalServiceConnector> mServiceConnector;
sp<IDataLoaderStatusListener> mListener;
+ int mStatus = IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
};
class MockDataLoaderManager : public DataLoaderManagerWrapper {
@@ -915,7 +918,7 @@
EXPECT_CALL(*mDataLoader, start(_)).Times(6);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(3);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1126,7 +1129,7 @@
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(2);
EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(2);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(5);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(6);
sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
NiceMock<MockStorageHealthListener>* listenerMock = listener.get();
@@ -1314,7 +1317,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1355,7 +1358,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
// System data loader.
mDataLoaderParcel.packageName = "android";
TemporaryDir tempDir;
@@ -1366,9 +1369,9 @@
ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
{}, {}));
- // No readlogs callback.
- ASSERT_EQ(mTimedQueue->mAfter, 0ms);
- ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+ // IfsState callback.
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
// Now advance clock for 1hr.
@@ -1376,6 +1379,8 @@
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
// Now advance clock for 2hrs.
mClock->advance(readLogsMaxInterval);
+ // IfsStorage callback should not affect anything.
+ callback();
ASSERT_EQ(mDataLoader->setStorageParams(true), 0);
}
@@ -1394,7 +1399,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1685,6 +1690,144 @@
mIncrementalService->registerLoadingProgressListener(storageId, listener);
}
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) {
+ mFs->hasFiles();
+
+ const auto stateUpdateInterval = 1s;
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ // No unbinding just yet.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // System data loader to get rid of readlog timeout callback.
+ mDataLoaderParcel.packageName = "android";
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // IfsState callback present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Not loaded yet.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+ // Send the callback, should not do anything.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Fully loaded.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)).WillOnce(Return(incfs::LoadingState::Full));
+ // Expect the unbind.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+
+ callback();
+
+ // Destroyed.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) {
+ mFs->hasFiles();
+
+ // Readlogs.
+ mVold->setIncFsMountOptionsSuccess();
+ mAppOpsManager->checkPermissionSuccess();
+
+ const auto stateUpdateInterval = 1s;
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ // No unbinding just yet.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // System data loader to get rid of readlog timeout callback.
+ mDataLoaderParcel.packageName = "android";
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // IfsState callback present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Not loaded yet.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+ // Send the callback, should not do anything.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Fully loaded.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::Full))
+ .WillOnce(Return(incfs::LoadingState::Full));
+ // But with readlogs.
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+
+ // Send the callback, still nothing.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Disable readlogs and expect the unbind.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ ASSERT_GE(mDataLoader->setStorageParams(false), 0);
+
+ callback();
+
+ // Destroyed.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) {
mIncFs->openMountSuccess();
sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
@@ -1801,7 +1944,7 @@
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
int storageId =
@@ -1829,7 +1972,6 @@
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
.WillOnce(Return(incfs::LoadingState::MissingBlocks))
.WillOnce(Return(incfs::LoadingState::MissingBlocks))
- .WillOnce(Return(incfs::LoadingState::Full))
.WillOnce(Return(incfs::LoadingState::Full));
// Mark DataLoader as 'system' so that readlogs don't pollute the timed queue.
@@ -1846,10 +1988,10 @@
{
// Timed callback present -> 0 progress.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
// Call it again.
timedCallback();
@@ -1857,10 +1999,10 @@
{
// Still present -> some progress.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
// Fully loaded but readlogs collection enabled.
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
@@ -1871,10 +2013,10 @@
{
// Still present -> fully loaded + readlogs.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
// Now disable readlogs.
ASSERT_GE(mDataLoader->setStorageParams(false), 0);
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/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index d5eda20..dce853a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -41,7 +41,12 @@
}
private val platformCompat: PlatformCompat = mockThrowOnUnmocked {
- whenever(isChangeEnabled(eq(DomainVerificationCollector.RESTRICT_DOMAINS), any())) {
+ whenever(
+ isChangeEnabledInternalNoLogging(
+ eq(DomainVerificationCollector.RESTRICT_DOMAINS),
+ any()
+ )
+ ) {
(arguments[1] as ApplicationInfo).targetSdkVersion >= Build.VERSION_CODES.S
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 7e25901..1b0a305 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -114,12 +114,7 @@
it,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
- whenever(
- isChangeEnabled(
- anyLong(),
- any()
- )
- ) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(connection)
}
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 0e74b65..ef79b08 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
@@ -310,7 +310,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index fe3672d..0ce16e6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -43,6 +43,7 @@
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import java.util.UUID
@@ -366,7 +367,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 377bae1..b7c6922 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -98,7 +98,7 @@
context,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(),any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(connection)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 44c1b8f..54648ab 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -71,7 +71,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 7925b69..0fcda81 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -660,15 +660,19 @@
new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
markImplicitConstraintsSatisfied(job, false);
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), false, false);
assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
markImplicitConstraintsSatisfied(job, true);
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), false, false);
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
}
@@ -677,7 +681,7 @@
job.setDeviceNotDozingConstraintSatisfied(
sElapsedRealtimeClock.millis(), isSatisfied, false);
job.setBackgroundNotRestrictedConstraintSatisfied(
- sElapsedRealtimeClock.millis(), isSatisfied);
+ sElapsedRealtimeClock.millis(), isSatisfied, false);
}
private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
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 f73af53..ee1a4f4 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
@@ -384,7 +384,8 @@
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
/* state */ true, /* allowlisted */false);
- js.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ js.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
return js;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
index 0597443..ef48646 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
@@ -19,8 +19,6 @@
import android.os.IPowerManager;
import android.os.PowerManager.LocationPowerSaveMode;
-import com.android.server.location.eventlog.LocationEventLog;
-
/**
* Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
* change".
@@ -30,8 +28,7 @@
@LocationPowerSaveMode
private int mLocationPowerSaveMode;
- public FakeLocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
- super(locationEventLog);
+ public FakeLocationPowerSaveModeHelper() {
mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
index 6156ba9..28da027 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
@@ -43,7 +43,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
import org.junit.After;
@@ -85,7 +84,7 @@
Context context = mock(Context.class);
doReturn(powerManager).when(context).getSystemService(PowerManager.class);
- mHelper = new SystemLocationPowerSaveModeHelper(context, new LocationEventLog());
+ mHelper = new SystemLocationPowerSaveModeHelper(context);
mHelper.onSystemReady();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 1f102ac..ae70dad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -16,8 +16,6 @@
package com.android.server.location.injector;
-import com.android.server.location.eventlog.LocationEventLog;
-
public class TestInjector implements Injector {
private final FakeUserInfoHelper mUserInfoHelper;
@@ -35,17 +33,13 @@
private final LocationUsageLogger mLocationUsageLogger;
public TestInjector() {
- this(new LocationEventLog());
- }
-
- public TestInjector(LocationEventLog eventLog) {
mUserInfoHelper = new FakeUserInfoHelper();
mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
mAppForegroundHelper = new FakeAppForegroundHelper();
- mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
+ mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper();
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
mDeviceIdleHelper = new FakeDeviceIdleHelper();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 1b58e92..24b85f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -83,7 +83,6 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
@@ -161,19 +160,17 @@
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
- LocationEventLog eventLog = new LocationEventLog();
-
- mInjector = new TestInjector(eventLog);
+ mInjector = new TestInjector();
mInjector.getUserInfoHelper().startUser(OTHER_USER);
- mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog);
+ mPassive = new PassiveLocationProviderManager(mContext, mInjector);
mPassive.startManager();
mPassive.setRealProvider(new PassiveLocationProvider(mContext));
mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY);
mProvider.setProviderAllowed(true);
- mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive);
+ mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
mManager.startManager();
mManager.setRealProvider(mProvider);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index c3cca64..04e0151 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -36,7 +36,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.test.FakeProvider;
@@ -77,7 +76,7 @@
mDelegateProvider = new FakeProvider(mDelegate);
mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
- mDelegateProvider, new LocationEventLog());
+ mDelegateProvider);
mProvider.getController().setListener(mListener);
mProvider.getController().start();
}
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..6cdac1a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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 {
+
+ int numInvocations;
+
+ @Override
+ public void onUserStarted(int newUserId) {
+ 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);
+ mUserStoppedCallback.onUserStopped();
+ callback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+
+ private static class TestStartUserClient extends StartUserClient<Object> {
+ public TestStartUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStartedCallback 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());
+ callback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+}
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..557d07c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4037,16 +4037,16 @@
}
@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();
+ dpm.setEnterpriseNetworkPreferenceEnabled(false);
+ assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isFalse();
// TODO(b/178655595)
// verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
// any(UserHandle.class),
@@ -4055,8 +4055,8 @@
// any(Runnable.class)
//);
- dpm.setNetworkSlicingEnabled(true);
- assertThat(dpm.isNetworkSlicingEnabled()).isTrue();
+ dpm.setEnterpriseNetworkPreferenceEnabled(true);
+ assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isTrue();
// TODO(b/178655595)
// verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
// any(UserHandle.class),
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index a7b32ac..68a6e60 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -96,7 +96,7 @@
case ACTION_JOB_STOPPED:
mTestJobStatus.running = false;
mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = params.getStopReason();
+ mTestJobStatus.stopReason = params.getLegacyStopReason();
break;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
index 3ea86f2..87881bf 100644
--- a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
+++ b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
@@ -47,7 +47,7 @@
int reason = params.getStopReason();
int event = TestEnvironment.EVENT_STOP_JOB;
Log.d(TAG, "stop reason: " + String.valueOf(reason));
- if (reason == JobParameters.REASON_PREEMPT) {
+ if (reason == JobParameters.STOP_REASON_PREEMPT) {
event = TestEnvironment.EVENT_PREEMPT_JOB;
Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
}
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/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index 5a100a2..a0e9d97 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -72,7 +72,6 @@
private TestCallback mTestCallback;
private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider;
- private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
@Before
public void setUp() {
@@ -84,13 +83,10 @@
};
mTestThreadingDomain = new TestThreadingDomain();
mTestCallback = new TestCallback(mTestThreadingDomain);
- mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
- stubbedProviderMetricsLogger, mTestThreadingDomain, "primary",
- mTimeZoneAvailabilityChecker);
+ stubbedProviderMetricsLogger, mTestThreadingDomain, "primary");
mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
- stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary",
- mTimeZoneAvailabilityChecker);
+ stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary");
}
@Test
@@ -1185,10 +1181,9 @@
* Creates the instance.
*/
TestLocationTimeZoneProvider(ProviderMetricsLogger providerMetricsLogger,
- ThreadingDomain threadingDomain, String providerName,
- TimeZoneIdValidator timeZoneIdValidator) {
+ ThreadingDomain threadingDomain, String providerName) {
super(providerMetricsLogger, threadingDomain, providerName,
- timeZoneIdValidator);
+ new FakeTimeZoneProviderEventPreProcessor());
}
public void setFailDuringInitialization(boolean failInitialization) {
@@ -1321,14 +1316,4 @@
mTestProviderState.commitLatest();
}
}
-
- private static final class FakeTimeZoneIdValidator
- implements LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return true;
- }
-
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..e75d05c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.timezonedetector.location;
+
+/**
+ * Fake implementation of {@link TimeZoneProviderEventPreProcessor} which assumes that all events
+ * are valid or always uncertain if {@link #enterUncertainMode()} was called.
+ */
+public final class FakeTimeZoneProviderEventPreProcessor
+ implements TimeZoneProviderEventPreProcessor {
+
+ private boolean mIsUncertain = false;
+
+ @Override
+ public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
+ if (mIsUncertain) {
+ return TimeZoneProviderEvent.createUncertainEvent();
+ }
+ return timeZoneProviderEvent;
+ }
+
+ public void enterUncertainMode() {
+ mIsUncertain = true;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index d13a04e..0edb559 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -52,10 +52,8 @@
import java.time.Duration;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -68,13 +66,13 @@
private TestThreadingDomain mTestThreadingDomain;
private TestProviderListener mProviderListener;
- private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
+ private FakeTimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
@Before
public void setUp() {
mTestThreadingDomain = new TestThreadingDomain();
mProviderListener = new TestProviderListener();
- mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
+ mTimeZoneProviderEventPreProcessor = new FakeTimeZoneProviderEventPreProcessor();
}
@Test
@@ -82,9 +80,10 @@
String providerName = "arbitrary";
RecordingProviderMetricsLogger providerMetricsLogger = new RecordingProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
- mTimeZoneAvailabilityChecker.validIds("Europe/London");
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
// initialize()
provider.initialize(mProviderListener);
@@ -174,8 +173,10 @@
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
AtomicReference<Bundle> resultReference = new AtomicReference<>();
@@ -193,10 +194,11 @@
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
- mTimeZoneAvailabilityChecker.validIds("Europe/London");
// initialize()
provider.initialize(mProviderListener);
@@ -234,14 +236,17 @@
}
@Test
- public void considerSuggestionWithInvalidTimeZoneIdsAsUncertain() {
+ public void entersUncertainState_whenEventHasUnsupportedZones() {
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
provider.initialize(mProviderListener);
+ mTimeZoneProviderEventPreProcessor.enterUncertainMode();
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
@@ -309,8 +314,9 @@
TestLocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
- @NonNull TimeZoneIdValidator timeZoneIdValidator) {
- super(providerMetricsLogger, threadingDomain, providerName, timeZoneIdValidator);
+ @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
+ super(providerMetricsLogger,
+ threadingDomain, providerName, timeZoneProviderEventPreProcessor);
}
@Override
@@ -367,20 +373,6 @@
}
}
- private static final class FakeTimeZoneIdValidator
- implements LocationTimeZoneProvider.TimeZoneIdValidator {
- private final Set<String> mValidTimeZoneIds = new HashSet<>();
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return mValidTimeZoneIds.contains(timeZoneId);
- }
-
- public void validIds(String... timeZoneIdss) {
- mValidTimeZoneIds.addAll(asList(timeZoneIdss));
- }
- }
-
private static class StubbedProviderMetricsLogger implements
LocationTimeZoneProvider.ProviderMetricsLogger {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
deleted file mode 100644
index 5561b2c..0000000
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.timezonedetector.location;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.TimeZone;
-
-@Presubmit
-public class ZoneInfoDbTimeZoneIdValidatorTest {
- private final LocationTimeZoneProvider.TimeZoneIdValidator mTzChecker =
- new ZoneInfoDbTimeZoneIdValidator();
-
- @Test
- public void timeZoneIdsFromZoneInfoDbAreValid() {
- for (String timeZone : TimeZone.getAvailableIDs()) {
- assertWithMessage("Time zone %s should be supported", timeZone)
- .that(mTzChecker.isValid(timeZone)).isTrue();
- }
- }
-
- @Test
- public void nonExistingZones_areNotSupported() {
- List<String> nonExistingTimeZones = Arrays.asList(
- "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30"
- );
-
- for (String timeZone : nonExistingTimeZones) {
- assertWithMessage(timeZone + " is not a valid time zone")
- .that(mTzChecker.isValid(timeZone))
- .isFalse();
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
new file mode 100644
index 0000000..173705be
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.timezonedetector.location;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Presubmit;
+import android.service.timezone.TimeZoneProviderSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.TimeZone;
+
+/** Tests for {@link ZoneInfoDbTimeZoneProviderEventPreProcessor}. */
+@Presubmit
+public class ZoneInfoDbTimeZoneProviderEventPreProcessorTest {
+
+ private static final long ARBITRARY_TIME_MILLIS = 11223344;
+
+ private final ZoneInfoDbTimeZoneProviderEventPreProcessor mPreProcessor =
+ new ZoneInfoDbTimeZoneProviderEventPreProcessor();
+
+ @Test
+ public void timeZoneIdsFromZoneInfoDbAreValid() {
+ for (String timeZone : TimeZone.getAvailableIDs()) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ assertWithMessage("Time zone %s should be supported", timeZone)
+ .that(mPreProcessor.preProcess(event)).isEqualTo(event);
+ }
+ }
+
+ @Test
+ public void eventWithNonExistingZones_areMappedToUncertainEvent() {
+ List<String> nonExistingTimeZones = Arrays.asList(
+ "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
+
+ for (String timeZone : nonExistingTimeZones) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+
+ assertWithMessage(timeZone + " is not a valid time zone")
+ .that(mPreProcessor.preProcess(event))
+ .isEqualTo(TimeZoneProviderEvent.createUncertainEvent());
+ }
+ }
+
+ private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
+ return TimeZoneProviderEvent.createSuggestionEvent(
+ new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(Arrays.asList(timeZoneIds))
+ .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
+ .build());
+ }
+
+}
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..a959a5e 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, (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..739a1a3 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, (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..0ba3a21b 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,8 @@
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.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -49,7 +51,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 +162,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 +180,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 +202,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 +217,13 @@
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));
-
- // 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);
+ verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
}
@Test
@@ -286,4 +276,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/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/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index b9ffd65..a37d5c8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -31,9 +31,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.media.session.MediaSession;
import android.os.Build;
-import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
@@ -73,7 +71,6 @@
private NotificationRecord mRecordMinCallNonInterruptive;
private NotificationRecord mRecordMinCall;
private NotificationRecord mRecordHighCall;
- private NotificationRecord mRecordDefaultMedia;
private NotificationRecord mRecordEmail;
private NotificationRecord mRecordInlineReply;
private NotificationRecord mRecordSms;
@@ -139,15 +136,6 @@
new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification n3 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
- .setStyle(new Notification.MediaStyle()
- .setMediaSession(new MediaSession.Token(Process.myUid(), null)))
- .build();
- mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
- "", 1499), getDefaultChannel());
- mRecordDefaultMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
-
Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setStyle(new Notification.MessagingStyle("sender!")).build();
mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
@@ -218,7 +206,7 @@
.setStyle(new Notification.MediaStyle())
.build();
mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification(
- pkg2, pkg2, 1, "cheater", uid2, uid2, n12, new UserHandle(userId),
+ pkg2, pkg2, 1, "media", uid2, uid2, n12, new UserHandle(userId),
"", 9258), getDefaultChannel());
mNoMediaSessionMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
@@ -247,7 +235,6 @@
final List<NotificationRecord> expected = new ArrayList<>();
expected.add(mRecordColorizedCall);
expected.add(mRecordColorized);
- expected.add(mRecordDefaultMedia);
expected.add(mRecordHighCall);
expected.add(mRecordInlineReply);
if (mRecordSms != null) {
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 32e5200..adf8fa4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -46,6 +46,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -1267,6 +1268,42 @@
is(Configuration.ORIENTATION_PORTRAIT));
}
+ @Test
+ public void testHybridRotationAnimation() {
+ final DisplayContent displayContent = mDefaultDisplay;
+ final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
+ final WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ final WindowState[] windows = { statusBar, navBar, app };
+ makeWindowVisible(windows);
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
+ displayPolicy.addWindowLw(navBar, navBar.mAttrs);
+ final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
+ displayContent.getRotation());
+ spyOn(rotationAnim);
+ // Assume that the display rotation is changed so it is frozen in preparation for animation.
+ doReturn(true).when(rotationAnim).hasScreenshot();
+ mWm.mDisplayFrozen = true;
+ displayContent.setRotationAnimation(rotationAnim);
+ // The fade rotation animation also starts to hide some non-app windows.
+ assertNotNull(displayContent.getFadeRotationAnimationController());
+ assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
+
+ for (WindowState w : windows) {
+ w.setOrientationChanging(true);
+ }
+ // The display only waits for the app window to unfreeze.
+ assertFalse(displayContent.waitForUnfreeze(statusBar));
+ assertFalse(displayContent.waitForUnfreeze(navBar));
+ assertTrue(displayContent.waitForUnfreeze(app));
+ // If all windows animated by fade rotation animation have done the orientation change,
+ // the animation controller should be cleared.
+ statusBar.setOrientationChanging(false);
+ navBar.setOrientationChanging(false);
+ assertNull(displayContent.getFadeRotationAnimationController());
+ }
+
@UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR })
@Test
public void testApplyTopFixedRotationTransform() {
@@ -1275,6 +1312,7 @@
doReturn(false).when(displayPolicy).navigationBarCanMove();
displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+ makeWindowVisible(mStatusBarWindow, mNavBarWindow);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
@@ -1297,7 +1335,7 @@
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
- assertNotNull(mDisplayContent.getFixedRotationAnimationController());
+ assertNotNull(mDisplayContent.getFadeRotationAnimationController());
assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
ANIMATION_TYPE_FIXED_TRANSFORM));
assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
@@ -1381,7 +1419,7 @@
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
- assertNull(mDisplayContent.getFixedRotationAnimationController());
+ assertNull(mDisplayContent.getFadeRotationAnimationController());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 7a4ad74..f97e794 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -556,11 +556,11 @@
}
@Test
- public void testNotAttachNavigationBar_controlledByFixedRotationAnimation() {
+ public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
setupForShouldAttachNavBarDuringTransition();
- FixedRotationAnimationController mockController =
- mock(FixedRotationAnimationController.class);
- doReturn(mockController).when(mDefaultDisplay).getFixedRotationAnimationController();
+ FadeRotationAnimationController mockController =
+ mock(FadeRotationAnimationController.class);
+ doReturn(mockController).when(mDefaultDisplay).getFadeRotationAnimationController();
final ActivityRecord homeActivity = createHomeActivity();
initializeRecentsAnimationController(mController, homeActivity);
assertFalse(mController.isNavigationBarAttachedToApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 956c277..37da529 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -578,10 +578,10 @@
}
@Test
- public void testNonAppTarget_notSendNavBar_controlledByFixedRotation() throws Exception {
- final FixedRotationAnimationController mockController =
- mock(FixedRotationAnimationController.class);
- doReturn(mockController).when(mDisplayContent).getFixedRotationAnimationController();
+ public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
+ final FadeRotationAnimationController mockController =
+ mock(FadeRotationAnimationController.class);
+ doReturn(mockController).when(mDisplayContent).getFadeRotationAnimationController();
final int transit = TRANSIT_OLD_TASK_OPEN;
setupForNonAppTargetNavBar(transit, true);
diff --git a/services/translation/java/com/android/server/translation/RemoteTranslationService.java b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
index 0c7e617..82cb728 100644
--- a/services/translation/java/com/android/server/translation/RemoteTranslationService.java
+++ b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
@@ -20,9 +20,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.os.ResultReceiver;
import android.service.translation.ITranslationService;
import android.service.translation.TranslationService;
import android.util.Slog;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import com.android.internal.infra.AbstractRemoteService;
@@ -80,8 +82,14 @@
return mIdleUnbindTimeoutMs;
}
- public void onSessionCreated(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
- run((s) -> s.onCreateTranslationSession(sourceSpec, destSpec, sessionId, resultReceiver));
+ public void onSessionCreated(@NonNull TranslationContext translationContext, int sessionId,
+ IResultReceiver resultReceiver) {
+ run((s) -> s.onCreateTranslationSession(translationContext, sessionId, resultReceiver));
+ }
+
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ run((s) -> s.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, resultReceiver));
}
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 72e1e33..6203ae9 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -34,6 +34,7 @@
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.translation.ITranslationManager;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
@@ -142,29 +143,33 @@
}
final class TranslationManagerServiceStub extends ITranslationManager.Stub {
+
@Override
- public void getSupportedLocales(IResultReceiver receiver, int userId)
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ ResultReceiver receiver, int userId)
throws RemoteException {
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
- || isCalledByServiceAppLocked(userId, "getSupportedLocales"))) {
- service.getSupportedLocalesLocked(receiver);
+ || isCalledByServiceAppLocked(userId, "getTranslationCapabilities"))) {
+ service.onTranslationCapabilitiesRequestLocked(sourceFormat, targetFormat,
+ receiver);
} else {
- Slog.v(TAG, "getSupportedLocales(): no service for " + userId);
+ Slog.v(TAG, "onGetTranslationCapabilitiesLocked(): no service for " + userId);
receiver.send(STATUS_SYNC_CALL_FAIL, null);
}
}
}
@Override
- public void onSessionCreated(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ public void onSessionCreated(TranslationContext translationContext,
int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId, "onSessionCreated"))) {
- service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver);
+ service.onSessionCreatedLocked(translationContext, sessionId, receiver);
} else {
Slog.v(TAG, "onSessionCreated(): no service for " + userId);
receiver.send(STATUS_SYNC_CALL_FAIL, null);
@@ -174,7 +179,7 @@
@Override
public void updateUiTranslationStateByTaskId(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
int taskId, int userId) {
// deprecated
enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
@@ -183,7 +188,7 @@
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId,
"updateUiTranslationStateByTaskId"))) {
- service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+ service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
taskId);
}
}
@@ -191,14 +196,14 @@
@Override
public void updateUiTranslationState(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
IBinder token, int taskId, int userId) {
enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
- service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+ service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
token, taskId);
}
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 1ca07cb..ee5ec47 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.translation;
-import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE;
import static android.view.translation.UiTranslationManager.EXTRA_STATE;
import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE;
@@ -31,23 +30,23 @@
import android.os.IRemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.translation.TranslationServiceInfo;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.inputmethod.InputMethodInfo;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
-import com.android.internal.util.SyncResultReceiver;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
-import java.util.ArrayList;
import java.util.List;
final class TranslationManagerServiceImpl extends
@@ -122,28 +121,28 @@
}
@GuardedBy("mLock")
- void getSupportedLocalesLocked(@NonNull IResultReceiver resultReceiver) {
- // TODO: implement this
- try {
- resultReceiver.send(STATUS_SYNC_CALL_SUCCESS,
- SyncResultReceiver.bundleFor(new ArrayList<>()));
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException returning supported locales: " + e);
+ void onTranslationCapabilitiesRequestLocked(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int destFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
+ if (remoteService != null) {
+ remoteService.onTranslationCapabilitiesRequest(sourceFormat, destFormat,
+ resultReceiver);
}
}
@GuardedBy("mLock")
- void onSessionCreatedLocked(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ void onSessionCreatedLocked(@NonNull TranslationContext translationContext, int sessionId,
+ IResultReceiver resultReceiver) {
final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
if (remoteService != null) {
- remoteService.onSessionCreated(sourceSpec, destSpec, sessionId, resultReceiver);
+ remoteService.onSessionCreated(translationContext, sessionId, resultReceiver);
}
}
@GuardedBy("mLock")
public void updateUiTranslationStateLocked(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
int taskId) {
// deprecated
final ActivityTokens taskTopActivityTokens =
@@ -152,13 +151,13 @@
Slog.w(TAG, "Unknown activity to query for update translation state.");
return;
}
- updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
- viewIds);
+ updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+ targetSpec, viewIds);
}
@GuardedBy("mLock")
public void updateUiTranslationStateLocked(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
IBinder token, int taskId) {
// Get top activity for a given task id
final ActivityTokens taskTopActivityTokens =
@@ -169,20 +168,20 @@
+ "translation state for token=" + token + " taskId=" + taskId);
return;
}
- updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
- viewIds);
+ updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+ targetSpec, viewIds);
}
private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens,
- @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec,
+ @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec targetSpec,
List<AutofillId> viewIds) {
try {
tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state,
- sourceSpec, destSpec, viewIds);
+ sourceSpec, targetSpec, viewIds);
} catch (RemoteException e) {
Slog.w(TAG, "Update UiTranslationState fail: " + e);
}
- invokeCallbacks(state, sourceSpec, destSpec);
+ invokeCallbacks(state, sourceSpec, targetSpec);
}
private void invokeCallbacks(
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 4ae11b8..23cf511 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -447,6 +447,9 @@
DataFailCause.VSNCP_RECONNECT_NOT_ALLOWED,
DataFailCause.IPV6_PREFIX_UNAVAILABLE,
DataFailCause.HANDOFF_PREFERENCE_CHANGED,
+ DataFailCause.SLICE_REJECTED,
+ DataFailCause.MATCH_ALL_RULE_NOT_ALLOWED,
+ DataFailCause.ALL_MATCHING_RULES_FAILED,
DataFailCause.OEM_DCFAILCAUSE_1,
DataFailCause.OEM_DCFAILCAUSE_2,
DataFailCause.OEM_DCFAILCAUSE_3,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f7580d7..a46621a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -630,7 +630,7 @@
D2D_SHARING_STARRED_CONTACTS,
D2D_SHARING_ALL
})
- public @interface DeviceToDeviceStatusSharing {}
+ public @interface DeviceToDeviceStatusSharingPreference {}
/**
* TelephonyProvider column name for device to device sharing status.
@@ -3415,29 +3415,31 @@
* app uses this method to indicate with whom they wish to share device to device status
* information.
* @param sharing the status sharing preference
- * @param subId the unique Subscription ID in database
+ * @param subscriptionId the unique Subscription ID in database
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
- public void setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharing int sharing,
- int subId) {
+ public void setDeviceToDeviceStatusSharingPreference(
+ @DeviceToDeviceStatusSharingPreference int sharing, int subscriptionId) {
if (VDBG) {
- logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: " + subId);
+ logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: "
+ + subscriptionId);
}
- setSubscriptionPropertyHelper(subId, "setDeviceToDeviceSharingStatus",
- (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subId));
+ setSubscriptionPropertyHelper(subscriptionId, "setDeviceToDeviceSharingStatus",
+ (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subscriptionId));
}
/**
* Returns the user-chosen device to device status sharing preference
- * @param subId Subscription id of subscription
+ * @param subscriptionId Subscription id of subscription
* @return The device to device status sharing preference
*/
- public @DeviceToDeviceStatusSharing int getDeviceToDeviceStatusSharing(int subId) {
+ public @DeviceToDeviceStatusSharingPreference int getDeviceToDeviceStatusSharingPreference(
+ int subscriptionId) {
if (VDBG) {
- logd("[getDeviceToDeviceStatusSharing] + subId: " + subId);
+ logd("[getDeviceToDeviceStatusSharing] + subId: " + subscriptionId);
}
- return getIntegerSubscriptionProperty(subId, D2D_STATUS_SHARING, D2D_SHARING_DISABLED,
- mContext);
+ return getIntegerSubscriptionProperty(subscriptionId, D2D_STATUS_SHARING,
+ D2D_SHARING_DISABLED, mContext);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a7f6204..d3246ca 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11037,6 +11037,25 @@
}
/**
+ * Determines the {@link PhoneAccountHandle} associated with this TelephonyManager.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+ *
+ * <p>Requires Permission android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE or that the
+ * calling app has carrier privileges (see {@link #hasCarrierPrivileges})
+ *
+ * @return The {@link PhoneAccountHandle} associated with the TelphonyManager, or {@code null}
+ * if there is no associated {@link PhoneAccountHandle}; this can happen if the subscription is
+ * data-only or an opportunistic subscription.
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @Nullable PhoneAccountHandle getPhoneAccountHandle() {
+ return getPhoneAccountHandleForSubscriptionId(getSubId());
+ }
+
+ /**
* Determines the {@link PhoneAccountHandle} associated with a subscription Id.
*
* @param subscriptionId The subscription Id to check.
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 52d0f03..a133ead 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -29,7 +29,9 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Contains the User Capability Exchange capabilities corresponding to a contact's URI.
@@ -110,7 +112,6 @@
/**
* Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
* queried through SIP OPTIONS.
- * @hide
*/
public static final class OptionsBuilder {
@@ -151,7 +152,7 @@
* @param tags the list of the supported feature tags
* @return this OptionBuilder
*/
- public @NonNull OptionsBuilder addFeatureTags(@NonNull List<String> tags) {
+ public @NonNull OptionsBuilder addFeatureTags(@NonNull Set<String> tags) {
mCapabilities.mFeatureTags.addAll(tags);
return this;
}
@@ -220,7 +221,7 @@
private @CapabilityMechanism int mCapabilityMechanism;
private @RequestResult int mRequestResult;
- private final List<String> mFeatureTags = new ArrayList<>();
+ private final Set<String> mFeatureTags = new HashSet<>();
private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();
private RcsContactUceCapability(@NonNull Uri contactUri, @CapabilityMechanism int mechanism,
@@ -235,7 +236,9 @@
mCapabilityMechanism = in.readInt();
mSourceType = in.readInt();
mRequestResult = in.readInt();
- in.readStringList(mFeatureTags);
+ List<String> featureTagList = new ArrayList<>();
+ in.readStringList(featureTagList);
+ mFeatureTags.addAll(featureTagList);
in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
}
@@ -245,7 +248,7 @@
out.writeInt(mCapabilityMechanism);
out.writeInt(mSourceType);
out.writeInt(mRequestResult);
- out.writeStringList(mFeatureTags);
+ out.writeStringList(new ArrayList<>(mFeatureTags));
out.writeParcelableList(mPresenceTuples, flags);
}
@@ -285,7 +288,20 @@
if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
return Collections.emptyList();
}
- return Collections.unmodifiableList(mFeatureTags);
+ return Collections.unmodifiableList(new ArrayList<>(mFeatureTags));
+ }
+
+ /**
+ * @return The feature tags present in the OPTIONS response from the network.
+ * <p>
+ * Note: this is only populated if {@link #getCapabilityMechanism} is
+ * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS}
+ */
+ public @NonNull Set<String> getFeatureTags() {
+ if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
+ return Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(mFeatureTags);
}
/**
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index a217d13..c3d7325 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -26,7 +26,8 @@
import android.telephony.ims.stub.CapabilityExchangeEventListener;
import android.util.Log;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
/**
* The ICapabilityExchangeEventListener wrapper class to store the listener which is registered by
@@ -84,7 +85,7 @@
* request to the framework.
*/
public void onRemoteCapabilityRequest(@NonNull Uri contactUri,
- @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
+ @NonNull Set<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
throws ImsException {
ICapabilityExchangeEventListener listener = mListenerBinder;
if (listener == null) {
@@ -114,7 +115,8 @@
};
try {
- listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback);
+ listener.onRemoteCapabilityRequest(contactUri, new ArrayList<>(remoteCapabilities),
+ internalCallback);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote capability request exception: " + e);
throw new ImsException("Remote is not available",
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 85703f8..6315b24 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -47,6 +47,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
@@ -145,8 +146,8 @@
throws RemoteException {
OptionsResponseCallback callbackWrapper = new RcsOptionsResponseAidlWrapper(callback);
executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal()
- .sendOptionsCapabilityRequest(contactUri, myCapabilities, callbackWrapper),
- "sendOptionsCapabilityRequest");
+ .sendOptionsCapabilityRequest(contactUri, new HashSet<>(myCapabilities),
+ callbackWrapper), "sendOptionsCapabilityRequest");
}
// Call the methods with a clean calling identity on the executor and wait indefinitely for
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index 6295548..a3be8da 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -26,7 +26,7 @@
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
-import java.util.List;
+import java.util.Set;
/**
* The interface that is used by the framework to listen to events from the vendor RCS stack
@@ -98,7 +98,8 @@
* {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError}.
* @param contactUri The URI associated with the remote contact that is
* requesting capabilities.
- * @param remoteCapabilities The remote contact's capability information.
+ * @param remoteCapabilities The remote contact's capability information. The capability
+ * information is in the format defined in RCC.07 section 2.6.1.3.
* @param callback The callback of this request which is sent from the remote user.
* @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
* currently connected to the framework. This can happen if the {@link RcsFeature} is not
@@ -107,6 +108,6 @@
* cases when the Telephony stack has crashed.
*/
void onRemoteCapabilityRequest(@NonNull Uri contactUri,
- @NonNull List<String> remoteCapabilities,
+ @NonNull Set<String> remoteCapabilities,
@NonNull OptionsRequestCallback callback) throws ImsException;
}
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index 03e17fb..25b9446 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -33,6 +33,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -433,6 +434,7 @@
* @param contactUri The URI of the remote user that we wish to get the capabilities of.
* @param myCapabilities The capabilities of this device to send to the remote user.
* @param callback The callback of this request which is sent from the remote user.
+ * @hide
*/
// executor used is defined in the constructor.
@SuppressLint("ExecutorRegistration")
@@ -446,4 +448,27 @@
// Do not do anything, this is a stub implementation.
}
}
+
+ /**
+ * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism
+ * in order to receive the capabilities of the remote user in response.
+ * <p>
+ * The implementer must use {@link OptionsResponseCallback} to send the response of
+ * this query from the network back to the framework.
+ * @param contactUri The URI of the remote user that we wish to get the capabilities of.
+ * @param myCapabilities The capabilities of this device to send to the remote user.
+ * @param callback The callback of this request which is sent from the remote user.
+ */
+ // executor used is defined in the constructor.
+ @SuppressLint("ExecutorRegistration")
+ public void sendOptionsCapabilityRequest(@NonNull Uri contactUri,
+ @NonNull Set<String> myCapabilities, @NonNull OptionsResponseCallback callback) {
+ // Stub - to be implemented by service
+ Log.w(LOG_TAG, "sendOptionsCapabilityRequest called with no implementation.");
+ try {
+ callback.onCommandError(COMMAND_CODE_NOT_SUPPORTED);
+ } catch (ImsException e) {
+ // Do not do anything, this is a stub implementation.
+ }
+ }
}
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
index b9b347b..c1a86b3 100644
--- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -87,7 +87,7 @@
assertEquals(down.id, eventId)
// Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
- assigner.onChoreographerCallback()
+ assigner.notifyFrameProcessed()
eventId = assigner.processEvent(move3)
assertEquals(move3.id, eventId)
eventId = assigner.processEvent(move4)
@@ -122,7 +122,7 @@
eventId = assigner.processEvent(up)
// DOWN is only sticky for Motions, not for keys
assertEquals(up.id, eventId)
- assigner.onChoreographerCallback()
+ assigner.notifyFrameProcessed()
val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22)
eventId = assigner.processEvent(down2)
assertEquals(down2.id, eventId)
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/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index a4d8353..fd126ad 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -44,7 +44,7 @@
setPartialConnectivityAcceptable(false)
setUnvalidatedConnectivityAcceptable(true)
}.build()
- assertParcelSane(config, 10)
+ assertParcelSane(config, 12)
}
@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 d40b88c..9946515 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);
@@ -531,11 +515,22 @@
assertFalse(nc1.equalsNetCapabilities(nc2));
nc2.addUnwantedCapability(NET_CAPABILITY_INTERNET);
assertTrue(nc1.equalsNetCapabilities(nc2));
+ if (isAtLeastS()) {
+ // Remove a required capability doesn't affect unwanted capabilities.
+ // This is a behaviour change from S.
+ nc1.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
- nc1.removeCapability(NET_CAPABILITY_INTERNET);
- assertFalse(nc1.equalsNetCapabilities(nc2));
- nc2.removeCapability(NET_CAPABILITY_INTERNET);
- assertTrue(nc1.equalsNetCapabilities(nc2));
+ nc1.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+ assertFalse(nc1.equalsNetCapabilities(nc2));
+ nc2.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
+ } else {
+ nc1.removeCapability(NET_CAPABILITY_INTERNET);
+ assertFalse(nc1.equalsNetCapabilities(nc2));
+ nc2.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
+ }
}
@Test
@@ -596,11 +591,21 @@
// This will effectively move NOT_ROAMING capability from required to unwanted for nc1.
nc1.addUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
- 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));
+ if (isAtLeastS()) {
+ // 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);
+ }
nc1.setSSID(TEST_SSID);
nc2.combineCapabilities(nc1);
@@ -963,26 +968,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/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 0c2fb4e..db1334a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -216,7 +216,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 +235,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 +446,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;