diff options
369 files changed, 11989 insertions, 6811 deletions
diff --git a/Android.bp b/Android.bp index f47ee20ed7b4..6a47db1e22a4 100644 --- a/Android.bp +++ b/Android.bp @@ -669,7 +669,6 @@ java_defaults { // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. "gps_debug.conf", "icu4j-platform-compat-config", - "libcore-platform-compat-config", "protolog.conf.json.gz", "services-platform-compat-config", "documents-ui-compat-config", diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java index 999860fdf4da..e65abcfba3e4 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 @@ public class BlobStoreIdleJobService extends JobService { @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 4c8ab9385903..baec0c3f829b 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 @@ public class JobInfo implements Parcelable { 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 0d3e0016fca0..60f64757f65a 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.Parcelable; 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 @@ public class JobParameters implements Parcelable { */ // 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 @@ public class JobParameters implements Parcelable { } /** @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 @@ public class JobParameters implements Parcelable { 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 @@ public class JobParameters implements Parcelable { } /** - * 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 @@ public class JobParameters implements Parcelable { } 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 @@ public class JobParameters implements Parcelable { } 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 0f3d299291c5..fa7a2d362ffa 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 @@ public abstract class JobService extends Service { * 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 7833a037463c..6ae91a0917a4 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 @@ package com.android.server.job; 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 @@ public interface JobSchedulerInternal { /** * 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 b958c3f694bf..d94d638a7021 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 @@ class JobConcurrencyManager { 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 @@ class JobConcurrencyManager { int[] preferredUidForContext = mRecycledPreferredUidForContext; int[] workTypeForContext = mRecycledWorkTypeForContext; String[] preemptReasonForContext = mRecycledPreemptReasonForContext; + int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext; String[] shouldStopJobReason = mRecycledShouldStopJobReason; updateCounterConfigLocked(); @@ -528,6 +531,7 @@ class JobConcurrencyManager { 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 @@ class JobConcurrencyManager { 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 @@ class JobConcurrencyManager { // 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 @@ class JobConcurrencyManager { 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 @@ class JobConcurrencyManager { if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; + preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; } if (startingJob) { @@ -631,8 +639,13 @@ class JobConcurrencyManager { } // 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 @@ class JobConcurrencyManager { 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); } } } @@ -877,7 +891,7 @@ class JobConcurrencyManager { } // Only expedited jobs can replace expedited jobs. - if (js.shouldTreatAsExpeditedJob()) { + if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) { // Keep fg/bg user distinction. if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) { // Let any important bg user job replace a bg user expedited job. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java index 6ffac91d7098..02f912919878 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 @@ public final class JobPackageTracker { 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 @@ public final class JobPackageTracker { 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 2b08ba554404..8ac237e63877 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService } } - 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService * 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService * 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 @@ public class JobSchedulerService extends com.android.server.SystemService * {@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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService @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; @@ -2164,6 +2187,10 @@ public class JobSchedulerService extends com.android.server.SystemService */ @VisibleForTesting boolean isReadyToBeExecutedLocked(JobStatus job) { + return isReadyToBeExecutedLocked(job, true); + } + + boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) { final boolean jobReady = job.isReady(); if (DEBUG) { @@ -2202,7 +2229,7 @@ public class JobSchedulerService extends com.android.server.SystemService } final boolean jobPending = mPendingJobs.contains(job); - final boolean jobActive = isCurrentlyActiveLocked(job); + final boolean jobActive = rejectActive && isCurrentlyActiveLocked(job); if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() @@ -2276,7 +2303,7 @@ public class JobSchedulerService extends com.android.server.SystemService 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 +2394,9 @@ public class JobSchedulerService extends com.android.server.SystemService } @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 +2734,7 @@ public class JobSchedulerService extends com.android.server.SystemService 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 +2747,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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 +2954,13 @@ public class JobSchedulerService extends com.android.server.SystemService 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 +3195,9 @@ public class JobSchedulerService extends com.android.server.SystemService 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 +3462,7 @@ public class JobSchedulerService extends com.android.server.SystemService 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 9ef46df7dac5..790fae0860de 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -151,6 +151,14 @@ public final class JobServiceContext implements ServiceConnection { /** The absolute maximum amount of time the job can run */ private long mMaxExecutionTimeMillis; + /** + * The stop reason for a pending cancel. If there's not pending cancel, then the value should be + * {@link JobParameters#STOP_REASON_UNDEFINED}. + */ + private int mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + private int mPendingLegacyStopReason; + private String mPendingDebugStopReason; + // Debugging: reason this job was last stopped. public String mStoppedReason; @@ -328,6 +336,7 @@ public final class JobServiceContext implements ServiceConnection { mAvailable = false; mStoppedReason = null; mStoppedTime = 0; + job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob(); return true; } } @@ -354,8 +363,9 @@ public final class JobServiceContext implements ServiceConnection { /** 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 +397,8 @@ public final class JobServiceContext implements ServiceConnection { && (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 +625,8 @@ public final class JobServiceContext implements ServiceConnection { } @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 +634,21 @@ public final class JobServiceContext implements ServiceConnection { } return; } - mParams.setStopReason(stopReasonCode, debugReason); - if (stopReasonCode == JobParameters.REASON_PREEMPT) { + if (mRunningJob.startedAsExpeditedJob + && stopReasonCode == JobParameters.STOP_REASON_QUOTA) { + // EJs should be able to run for at least the min upper limit regardless of quota. + final long earliestStopTimeElapsed = + mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis; + final long nowElapsed = sElapsedRealtimeClock.millis(); + if (nowElapsed < earliestStopTimeElapsed) { + mPendingStopReason = stopReasonCode; + mPendingLegacyStopReason = legacyStopReason; + mPendingDebugStopReason = debugReason; + return; + } + } + mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason); + if (legacyStopReason == JobParameters.REASON_PREEMPT) { mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : NO_PREFERRED_UID; } @@ -774,6 +799,23 @@ public final class JobServiceContext implements ServiceConnection { closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping"); break; case VERB_EXECUTING: + if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) { + if (mService.isReadyToBeExecutedLocked(mRunningJob, false)) { + // Job became ready again while we were waiting to stop it (for example, + // the device was temporarily taken off the charger). Ignore the pending + // stop and see what the manager says. + mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + mPendingLegacyStopReason = 0; + mPendingDebugStopReason = null; + } else { + Slog.i(TAG, "JS was waiting to stop this job." + + " Sending onStop: " + getRunningJobNameLocked()); + mParams.setStopReason(mPendingStopReason, mPendingLegacyStopReason, + mPendingDebugStopReason); + sendStopMessageLocked(mPendingDebugStopReason); + break; + } + } final long latestStopTimeElapsed = mExecutionStartTimeElapsed + mMaxExecutionTimeMillis; final long nowElapsed = sElapsedRealtimeClock.millis(); @@ -781,7 +823,8 @@ public final class JobServiceContext implements ServiceConnection { // 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 +833,11 @@ public final class JobServiceContext implements ServiceConnection { 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 +891,12 @@ public final class JobServiceContext implements ServiceConnection { } 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 +907,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.hasContentTriggerConstraint()); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), - stopReason); + legacyStopReason); } catch (RemoteException e) { // Whatever. } @@ -878,8 +925,11 @@ public final class JobServiceContext implements ServiceConnection { mCancelled = false; service = null; mAvailable = true; + mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + mPendingLegacyStopReason = 0; + mPendingDebugStopReason = null; removeOpTimeOutLocked(); - mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule); + mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); } @@ -964,7 +1014,16 @@ public final class JobServiceContext implements ServiceConnection { pw.print(", "); TimeUtils.formatDuration( (mExecutionStartTimeElapsed + mMaxExecutionTimeMillis) - nowElapsed, pw); - pw.println("]"); + pw.print("]"); + if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) { + pw.print(" Pending stop because "); + pw.print(mPendingStopReason); + pw.print("/"); + pw.print(mPendingLegacyStopReason); + pw.print("/"); + pw.print(mPendingDebugStopReason); + } + pw.println(); pw.decreaseIndent(); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index aa8d98c01853..9cd3a8fa6624 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 @@ public final class JobStore { /** * 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 @@ public final class JobStore { 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 a230b23f03a4..548a1ac14391 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 @@ public final class BackgroundJobsController extends StateController { 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 6e542f346f81..df21d753ea1f 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 android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; 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 @@ public final class ConnectivityController extends RestrictingController implemen } } + 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 @@ public final class ConnectivityController extends RestrictingController implemen } // See if we match after relaxing any unmetered request - final NetworkCapabilities relaxed = new NetworkCapabilities.Builder( - jobStatus.getJob().getRequiredNetwork().networkCapabilities) - .removeCapability(NET_CAPABILITY_NOT_METERED).build(); - if (relaxed.satisfiedByNetworkCapabilities(capabilities)) { + final NetworkCapabilities.Builder builder = + copyCapabilities(jobStatus.getJob().getRequiredNetwork()); + builder.removeCapability(NET_CAPABILITY_NOT_METERED); + if (builder.build().satisfiedByNetworkCapabilities(capabilities)) { // TODO: treat this as "maybe" response; need to check quotas return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC; } else { @@ -716,13 +725,6 @@ public final class ConnectivityController extends RestrictingController implemen StateControllerProto.ConnectivityController.REQUESTED_STANDBY_EXCEPTION_UIDS, mRequestedWhitelistJobs.keyAt(i)); } - for (int i = 0; i < mAvailableNetworks.size(); i++) { - Network network = mAvailableNetworks.keyAt(i); - if (network != null) { - network.dumpDebug(proto, - StateControllerProto.ConnectivityController.AVAILABLE_NETWORKS); - } - } for (int i = 0; i < mTrackedJobs.size(); i++) { final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i); for (int j = 0; j < jobs.size(); j++) { @@ -736,12 +738,6 @@ public final class ConnectivityController extends RestrictingController implemen StateControllerProto.ConnectivityController.TrackedJob.INFO); proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID, js.getSourceUid()); - NetworkRequest rn = js.getJob().getRequiredNetwork(); - if (rn != null) { - rn.dumpDebug(proto, - StateControllerProto.ConnectivityController.TrackedJob - .REQUIRED_NETWORK); - } proto.end(jsToken); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index bad8dc1ad1cb..8d999e1e7e36 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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 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.text.format.DateFormat; 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 com.android.server.job.JobStatusShortInfoProto; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.function.Predicate; /** @@ -324,6 +328,12 @@ public final class JobStatus { /** The evaluated priority of the job when it started running. */ public int lastEvaluatedPriority; + /** + * Whether or not this particular JobStatus instance was treated as an EJ when it started + * running. This isn't copied over when a job is rescheduled. + */ + public boolean startedAsExpeditedJob = false; + // If non-null, this is work that has been enqueued for the job. public ArrayList<JobWorkItem> pendingWork; @@ -353,6 +363,9 @@ public final class JobStatus { */ 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 +422,9 @@ public final class JobStatus { /** 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 +536,15 @@ public final class JobStatus { // 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 +1065,11 @@ public final class JobStatus { mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed; } + @JobParameters.StopReason + public int getStopReason() { + return mReasonReadyToUnready; + } + /** * Return the fractional position of "now" within the "run time" window of * this job. @@ -1100,18 +1128,19 @@ public final class JobStatus { */ public boolean canRunInDoze() { return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 - || (shouldTreatAsExpeditedJob() + || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob) && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0); } boolean canRunInBatterySaver() { return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0 - || (shouldTreatAsExpeditedJob() + || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob) && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0); } boolean shouldIgnoreNetworkBlocking() { - return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob(); + return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 + || (shouldTreatAsExpeditedJob() || startedAsExpeditedJob); } /** @return true if the constraint was changed, false otherwise. */ @@ -1172,7 +1201,9 @@ public final class JobStatus { } /** @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 +1257,7 @@ public final class JobStatus { "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 +1276,81 @@ public final class JobStatus { 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 +1434,42 @@ public final class JobStatus { * 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; } @@ -1926,7 +2039,10 @@ public final class JobStatus { pw.println(serviceInfo != null); if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) { pw.print("readyWithinExpeditedQuota: "); - pw.println(mReadyWithinExpeditedQuota); + pw.print(mReadyWithinExpeditedQuota); + pw.print(" (started as EJ: "); + pw.print(startedAsExpeditedJob); + pw.println(")"); } pw.decreaseIndent(); @@ -2065,9 +2181,6 @@ public final class JobStatus { if (uriPerms != null) { uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS); } - if (job.getRequiredNetwork() != null) { - job.getRequiredNetwork().dumpDebug(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK); - } if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_DOWNLOAD_BYTES, mTotalNetworkDownloadBytes); @@ -2156,10 +2269,6 @@ public final class JobStatus { } } - if (network != null) { - network.dumpDebug(proto, JobStatusDumpProto.NETWORK); - } - if (pendingWork != null) { for (int i = 0; i < pendingWork.size(); i++) { dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i)); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 2b79969f4378..91189e4b6e74 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -849,10 +849,9 @@ public final class QuotaController extends StateController { return true; } // A job is within quota if one of the following is true: - // 1. it's already running (already executing expedited jobs should be allowed to finish) - // 2. the app is currently in the foreground - // 3. the app overall is within its quota - // 4. It's on the temp allowlist (or within the grace period) + // 1. the app is currently in the foreground + // 2. the app overall is within its quota + // 3. It's on the temp allowlist (or within the grace period) if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { return true; } @@ -873,13 +872,6 @@ public final class QuotaController extends StateController { return true; } - Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(), - jobStatus.getSourcePackageName()); - // Any already executing expedited jobs should be allowed to finish. - if (ejTimer != null && ejTimer.isRunning(jobStatus)) { - return true; - } - return 0 < getRemainingEJExecutionTimeLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); } @@ -4153,6 +4145,8 @@ public final class QuotaController extends StateController { pw.print(", "); if (js.shouldTreatAsExpeditedJob()) { pw.print("within EJ quota"); + } else if (js.startedAsExpeditedJob) { + pw.print("out of EJ quota"); } else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { pw.print("within regular quota"); } else { @@ -4163,6 +4157,8 @@ public final class QuotaController extends StateController { pw.print(getRemainingEJExecutionTimeLocked( js.getSourceUserId(), js.getSourcePackageName())); pw.print("ms remaining in EJ quota"); + } else if (js.startedAsExpeditedJob) { + pw.print("should be stopped after min execution time"); } else { pw.print(getRemainingExecutionTimeLocked(js)); pw.print("ms remaining in quota"); diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java index ac59f9542e99..2962b1017315 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 @@ import com.android.server.job.controllers.JobStatus; /** * 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 @@ public abstract class JobRestriction { 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 class JobRestriction { 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 954a5b8bdaa8..8b699e9b04c3 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 @@ public class ThermalStatusRestriction extends JobRestriction { 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 dad2d7db015d..bb501e89b3b3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -55,7 +55,9 @@ package android { field public static final String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final String BLUETOOTH = "android.permission.BLUETOOTH"; field public static final String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN"; + field public static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT"; field public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; + field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN"; field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS"; field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED"; field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS"; @@ -194,6 +196,7 @@ package android { field public static final String CONTACTS = "android.permission-group.CONTACTS"; field public static final String LOCATION = "android.permission-group.LOCATION"; field public static final String MICROPHONE = "android.permission-group.MICROPHONE"; + field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES"; field public static final String PHONE = "android.permission-group.PHONE"; field public static final String SENSORS = "android.permission-group.SENSORS"; field public static final String SMS = "android.permission-group.SMS"; @@ -5830,8 +5833,8 @@ package android.app { 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); } @@ -7183,6 +7186,7 @@ package android.app.admin { method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName); method public boolean isDeviceIdAttestationSupported(); method public boolean isDeviceOwnerApp(String); + method public boolean isEnterpriseNetworkPreferenceEnabled(); method public boolean isEphemeralUser(@NonNull android.content.ComponentName); method public boolean isKeyPairGrantedToWifiAuth(@NonNull String); method public boolean isLockTaskPermitted(String); @@ -7190,7 +7194,6 @@ package android.app.admin { method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName); - method public boolean isNetworkSlicingEnabled(); method public boolean isOrganizationOwnedDeviceWithManagedProfile(); method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName); method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -7245,6 +7248,7 @@ package android.app.admin { method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>); method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence); method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); + method public void setEnterpriseNetworkPreferenceEnabled(boolean); method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy); method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName); method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String); @@ -7264,7 +7268,6 @@ package android.app.admin { 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); @@ -7970,6 +7973,7 @@ package android.app.job { 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(); @@ -7978,6 +7982,21 @@ package android.app.job { 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 { @@ -21325,6 +21344,7 @@ package android.media { 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); @@ -21352,6 +21372,7 @@ package android.media { 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 @@ -21442,6 +21463,10 @@ package android.media { 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); } @@ -35523,6 +35548,7 @@ package android.provider { method public static android.net.Uri getUriForSubscriptionIdAndField(int, String); field public static final String AUTHORITY = "service-state"; field public static final android.net.Uri CONTENT_URI; + field public static final String DATA_NETWORK_TYPE = "data_network_type"; field public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection"; field public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric"; field public static final String VOICE_REG_STATE = "voice_reg_state"; @@ -42128,7 +42154,7 @@ package android.telephony { 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); @@ -42141,7 +42167,7 @@ package android.telephony { 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); @@ -52507,9 +52533,43 @@ package android.view.textservice { 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/module-lib-current.txt b/core/api/module-lib-current.txt index e3b7c8898039..8dc5c15d48ce 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -312,6 +312,10 @@ package android.os.storage { field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 0; // 0x0 } + public final class StorageVolume implements android.os.Parcelable { + method @NonNull public android.os.UserHandle getOwner(); + } + } package android.provider { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7bbabb663269..02fd463eb2e4 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -267,6 +267,7 @@ package android { field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"; + field public static final String SUGGEST_EXTERNAL_TIME = "android.permission.SUGGEST_EXTERNAL_TIME"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY"; field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; @@ -699,6 +700,9 @@ package android.app { 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 +1196,14 @@ package android.app.backup { 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 +5232,7 @@ package android.media { 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 +8502,7 @@ package android.os { 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 +8516,7 @@ package android.os { 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 +8530,7 @@ package android.os { 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 +10268,10 @@ package android.service.translation { 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"; @@ -11252,12 +11263,11 @@ package android.telephony { public final class PhoneCapability implements android.os.Parcelable { method public int describeContents(); - method public int getDeviceNrCapabilityBitmask(); - method @IntRange(from=1) public int getMaxActiveInternetData(); - method @IntRange(from=1) public int getMaxActivePacketSwitchedVoiceCalls(); + method @NonNull public int[] getDeviceNrCapabilities(); + method @IntRange(from=1) public int getMaxActiveDataSubscriptions(); + method @IntRange(from=1) public int getMaxActiveVoiceSubscriptions(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhoneCapability> CREATOR; - field public static final int DEVICE_NR_CAPABILITY_NONE = 0; // 0x0 field public static final int DEVICE_NR_CAPABILITY_NSA = 1; // 0x1 field public static final int DEVICE_NR_CAPABILITY_SA = 2; // 0x2 } @@ -14265,6 +14275,10 @@ package android.view.displayhash { 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 ae1cbf77dd8a..5f78bc5b4e1e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -34,6 +34,7 @@ package android { field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; + field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"; field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; @@ -817,6 +818,7 @@ package android.content.pm { method public int describeContents(); method public android.os.UserHandle getUserHandle(); method public boolean isAdmin(); + method public boolean isCloneProfile(); method public boolean isDemo(); method public boolean isEnabled(); method public boolean isEphemeral(); @@ -1702,6 +1704,7 @@ package android.os { method public static android.os.VibrationEffect get(int, boolean); method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context); method public abstract long getDuration(); + method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform(); field public static final int EFFECT_POP = 4; // 0x4 field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0 field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1 @@ -1711,35 +1714,30 @@ package android.os { 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(); + 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 void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.OneShot> CREATOR; + method public int getRepeatIndex(); + method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments(); + method @NonNull public android.os.VibrationEffect.Composed resolve(int); + method @NonNull public android.os.VibrationEffect.Composed scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR; } - public static 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 final class VibrationEffect.Composition { + method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect); + method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect, @IntRange(from=0) int); } - public static 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(); - 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; + public static final class VibrationEffect.WaveformBuilder { + method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect build(); + method @NonNull public android.os.VibrationEffect build(int); } public class VintfObject { @@ -1856,6 +1854,80 @@ package android.os.strictmode { } +package android.os.vibrator { + + public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int); + method public int describeContents(); + method public long getDuration(); + method public int getEffectId(); + method public int getEffectStrength(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.PrebakedSegment resolve(int); + method @NonNull public android.os.vibrator.PrebakedSegment scale(float); + method public boolean shouldFallback(); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR; + } + + public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int); + method public int describeContents(); + method public int getDelay(); + method public long getDuration(); + method public int getPrimitiveId(); + method public float getScale(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int); + method @NonNull public android.os.vibrator.PrimitiveSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR; + } + + public final class RampSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int); + method public int describeContents(); + method public long getDuration(); + method public float getEndAmplitude(); + method public float getEndFrequency(); + method public float getStartAmplitude(); + method public float getStartFrequency(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.RampSegment resolve(int); + method @NonNull public android.os.vibrator.RampSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR; + } + + public final class StepSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int); + method public int describeContents(); + method public float getAmplitude(); + method public long getDuration(); + method public float getFrequency(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.StepSegment resolve(int); + method @NonNull public android.os.vibrator.StepSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR; + } + + public abstract class VibrationEffectSegment implements android.os.Parcelable { + method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int); + method public abstract long getDuration(); + method public abstract boolean hasNonZeroAmplitude(); + method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int); + method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float); + method public abstract void validate(); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR; + } + +} + package android.permission { public final class PermissionControllerManager { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index a536efb2b488..1833ed50ce4f 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): 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.IncidentReportArgs#IncidentReportArgs(android.os.P 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.RaEvent: 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.net.metrics.IpConnectivityLog.Event: ParcelNotFinal: android.os.IncidentManager.IncidentReport: -ParcelNotFinal: android.os.VibrationEffect.OneShot: - -ParcelNotFinal: android.os.VibrationEffect.Prebaked: - -ParcelNotFinal: android.os.VibrationEffect.Waveform: - ParcelNotFinal: android.os.health.HealthStatsParceler: ParcelNotFinal: android.service.autofill.InternalOnClickAction: diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 27b19bcd31a1..a6aa28effe00 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1073,6 +1073,8 @@ public class AppOpsManager { /** @hide */ @UnsupportedAppUsage public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN; + /** @hide */ + public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT; /** @hide Use the BiometricPrompt/BiometricManager APIs. */ public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC; /** @hide Physical activity recognition. */ @@ -1221,7 +1223,7 @@ public class AppOpsManager { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 111; + public static final int _NUM_OP = 112; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1465,6 +1467,8 @@ public class AppOpsManager { public static final String OPSTR_START_FOREGROUND = "android:start_foreground"; /** @hide */ public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan"; + /** @hide */ + public static final String OPSTR_BLUETOOTH_CONNECT = "android:bluetooth_connect"; /** @hide Use the BiometricPrompt/BiometricManager APIs. */ public static final String OPSTR_USE_BIOMETRIC = "android:use_biometric"; @@ -1696,6 +1700,9 @@ public class AppOpsManager { OP_WRITE_MEDIA_VIDEO, OP_READ_MEDIA_IMAGES, OP_WRITE_MEDIA_IMAGES, + // Nearby devices + OP_BLUETOOTH_SCAN, + OP_BLUETOOTH_CONNECT, // APPOP PERMISSIONS OP_ACCESS_NOTIFICATIONS, @@ -1801,7 +1808,7 @@ public class AppOpsManager { OP_ACCEPT_HANDOVER, // ACCEPT_HANDOVER OP_MANAGE_IPSEC_TUNNELS, // MANAGE_IPSEC_HANDOVERS OP_START_FOREGROUND, // START_FOREGROUND - OP_COARSE_LOCATION, // BLUETOOTH_SCAN + OP_BLUETOOTH_SCAN, // BLUETOOTH_SCAN OP_USE_BIOMETRIC, // BIOMETRIC OP_ACTIVITY_RECOGNITION, // ACTIVITY_RECOGNITION OP_SMS_FINANCIAL_TRANSACTIONS, // SMS_FINANCIAL_TRANSACTIONS @@ -1835,6 +1842,7 @@ public class AppOpsManager { OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE OP_MANAGE_MEDIA, // MANAGE_MEDIA + OP_BLUETOOTH_CONNECT, // OP_BLUETOOTH_CONNECT }; /** @@ -1952,6 +1960,7 @@ public class AppOpsManager { OPSTR_FINE_LOCATION_SOURCE, OPSTR_COARSE_LOCATION_SOURCE, OPSTR_MANAGE_MEDIA, + OPSTR_BLUETOOTH_CONNECT, }; /** @@ -2070,6 +2079,7 @@ public class AppOpsManager { "FINE_LOCATION_SOURCE", "COARSE_LOCATION_SOURCE", "MANAGE_MEDIA", + "BLUETOOTH_CONNECT", }; /** @@ -2155,7 +2165,7 @@ public class AppOpsManager { Manifest.permission.ACCEPT_HANDOVER, Manifest.permission.MANAGE_IPSEC_TUNNELS, Manifest.permission.FOREGROUND_SERVICE, - null, // no permission for OP_BLUETOOTH_SCAN + Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.USE_BIOMETRIC, Manifest.permission.ACTIVITY_RECOGNITION, Manifest.permission.SMS_FINANCIAL_TRANSACTIONS, @@ -2189,6 +2199,7 @@ public class AppOpsManager { null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE, null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE, Manifest.permission.MANAGE_MEDIA, + Manifest.permission.BLUETOOTH_CONNECT, }; /** @@ -2308,6 +2319,7 @@ public class AppOpsManager { null, // ACCESS_FINE_LOCATION_SOURCE null, // ACCESS_COARSE_LOCATION_SOURCE null, // MANAGE_MEDIA + null, // BLUETOOTH_CONNECT }; /** @@ -2426,6 +2438,7 @@ public class AppOpsManager { null, // ACCESS_FINE_LOCATION_SOURCE null, // ACCESS_COARSE_LOCATION_SOURCE null, // MANAGE_MEDIA + null, // BLUETOOTH_CONNECT }; /** @@ -2543,6 +2556,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA + AppOpsManager.MODE_ALLOWED, // BLUETOOTH_CONNECT }; /** @@ -2664,6 +2678,7 @@ public class AppOpsManager { false, // ACCESS_FINE_LOCATION_SOURCE false, // ACCESS_COARSE_LOCATION_SOURCE false, // MANAGE_MEDIA + false, // BLUETOOTH_CONNECT }; /** diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 3bfddf7db015..e5a969ac98ad 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -244,6 +244,8 @@ interface IActivityTaskManager { 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 b6d25cfb26ce..4326c2d85500 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -133,6 +133,42 @@ public class KeyguardManager { */ 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 @@ public class KeyguardManager { 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 @@ public class KeyguardManager { 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 @@ public class KeyguardManager { userId); success = true; break; - case LockTypes.PIN: + case PIN: CharSequence pinStr = new String(password); lockPatternUtils.setLockCredential( LockscreenCredential.createPin(pinStr), @@ -775,7 +811,7 @@ public class KeyguardManager { userId); success = true; break; - case LockTypes.PATTERN: + case PATTERN: List<LockPatternView.Cell> pattern = LockPatternUtils.byteArrayToPattern(password); lockPatternUtils.setLockCredential( @@ -796,18 +832,4 @@ public class KeyguardManager { } 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 3a533c98cb60..4eda6fefd188 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -20,8 +20,6 @@ import static android.annotation.Dimension.DP; 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 @@ public class Notification implements Parcelable /** * {@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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable } private CharSequence processTextSpans(CharSequence text) { - if (hasForegroundColor() || mInNightMode) { + if (mInNightMode) { return ContrastColorUtil.clearColorSpans(text); } return text; @@ -5103,10 +5100,6 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable || 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable @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 @@ public class Notification implements Parcelable } } } - final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp; + final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; result.setRightIconState(largeIconShown, viewWidthDp, extraMarginEndDpIfVisible, expanderSizeDp); } @@ -5363,7 +5294,7 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable */ 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 @@ public class Notification implements Parcelable */ 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 @@ public class Notification implements Parcelable */ 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 @@ public class Notification implements Parcelable } /** - * 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable * @hide */ public boolean isColorized() { - if (isColorizedMedia()) { - return true; - } return extras.getBoolean(EXTRA_COLORIZED) && (hasColorizedPermission() || isForegroundService()); } @@ -6872,27 +6781,6 @@ public class Notification implements Parcelable } /** - * @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 @@ public class Notification implements Parcelable /** * @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 @@ public class Notification implements Parcelable * 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 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeContentView(boolean increasedHeight) { - return makeMediaContentView(); + return makeMediaContentView(null /* customContent */); } /** @@ -9049,7 +8928,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - return makeMediaBigContentView(); + return makeMediaBigContentView(null /* customContent */); } /** @@ -9057,7 +8936,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - return makeMediaContentView(); + return makeMediaContentView(null /* customContent */); } /** @hide */ @@ -9127,88 +9006,72 @@ public class Notification implements Parcelable container.setContentDescription(buttonId, action.title); } - private RemoteViews makeMediaContentView() { + /** @hide */ + protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) { + final int numActions = mBuilder.mActions.size(); + 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); - RemoteViews view = mBuilder.applyStandardTemplate( + TemplateBindResult result = new TemplateBindResult(); + RemoteViews template = mBuilder.applyStandardTemplate( R.layout.notification_template_material_media, p, null /* result */); - final int numActions = mBuilder.mActions.size(); - final int numActionsToShow = mActionsToShowInCompact == null - ? 0 - : Math.min(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)); - } 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 @@ public class Notification implements Parcelable */ @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 @@ public class Notification implements Parcelable */ @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 @@ public class Notification implements Parcelable */ @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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable boolean mHeaderless; boolean mHideAppName; boolean mHideTitle; + boolean mHideSubText; + boolean mHideTime; boolean mHideActions; boolean mHideProgress; boolean mHideSnoozeButton; @@ -12275,6 +12127,8 @@ public class Notification implements Parcelable mHeaderless = false; mHideAppName = false; mHideTitle = false; + mHideSubText = false; + mHideTime = false; mHideActions = false; mHideProgress = false; mHideSnoozeButton = false; @@ -12288,6 +12142,7 @@ public class Notification implements Parcelable summaryText = null; headerTextSecondary = null; maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; + hideLargeIcon = false; allowColorization = true; mReduceHighlights = false; return this; @@ -12312,6 +12167,16 @@ public class Notification implements Parcelable 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 6b2e649b0c69..d640a6f49670 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -189,7 +189,7 @@ public final class WallpaperColors implements Parcelable { } 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 426159f43095..117df02a2d7c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9990,26 +9990,24 @@ public class DevicePolicyManager { } /** - * 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 @@ public class DevicePolicyManager { } /** - * 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 7901791fc7d4..0ad92b70168e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -268,8 +268,8 @@ interface IDevicePolicyManager { 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 675934629f2d..51430c059768 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 @@ public class RestoreSet implements Parcelable { * 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 class RestoreSet implements Parcelable { */ 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; + } + + /** + * 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); } - 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. + * @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 @@ public class RestoreSet implements Parcelable { 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 @@ public class RestoreSet implements Parcelable { name = in.readString(); device = in.readString(); token = in.readLong(); + backupTransportFlags = in.readInt(); } } diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java index dd2ba7db03ae..e645831a31b1 100644 --- a/core/java/android/app/people/PeopleSpaceTile.java +++ b/core/java/android/app/people/PeopleSpaceTile.java @@ -56,6 +56,7 @@ public class PeopleSpaceTile implements Parcelable { private CharSequence mNotificationContent; private String mNotificationCategory; private Uri mNotificationDataUri; + private int mMessagesCount; private Intent mIntent; private long mNotificationTimestamp; private List<ConversationStatus> mStatuses; @@ -74,6 +75,7 @@ public class PeopleSpaceTile implements Parcelable { mNotificationContent = b.mNotificationContent; mNotificationCategory = b.mNotificationCategory; mNotificationDataUri = b.mNotificationDataUri; + mMessagesCount = b.mMessagesCount; mIntent = b.mIntent; mNotificationTimestamp = b.mNotificationTimestamp; mStatuses = b.mStatuses; @@ -140,6 +142,10 @@ public class PeopleSpaceTile implements Parcelable { return mNotificationDataUri; } + public int getMessagesCount() { + return mMessagesCount; + } + /** * Provides an intent to launch. If present, we should manually launch the intent on tile * click, rather than calling {@link android.content.pm.LauncherApps} to launch the shortcut ID. @@ -175,6 +181,7 @@ public class PeopleSpaceTile implements Parcelable { builder.setNotificationContent(mNotificationContent); builder.setNotificationCategory(mNotificationCategory); builder.setNotificationDataUri(mNotificationDataUri); + builder.setMessagesCount(mMessagesCount); builder.setIntent(mIntent); builder.setNotificationTimestamp(mNotificationTimestamp); builder.setStatuses(mStatuses); @@ -196,6 +203,7 @@ public class PeopleSpaceTile implements Parcelable { private CharSequence mNotificationContent; private String mNotificationCategory; private Uri mNotificationDataUri; + private int mMessagesCount; private Intent mIntent; private long mNotificationTimestamp; private List<ConversationStatus> mStatuses; @@ -320,6 +328,12 @@ public class PeopleSpaceTile implements Parcelable { return this; } + /** Sets the number of messages associated with the Tile. */ + public Builder setMessagesCount(int messagesCount) { + mMessagesCount = messagesCount; + return this; + } + /** Sets an intent to launch on click. */ public Builder setIntent(Intent intent) { mIntent = intent; @@ -359,6 +373,7 @@ public class PeopleSpaceTile implements Parcelable { mNotificationContent = in.readCharSequence(); mNotificationCategory = in.readString(); mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader()); + mMessagesCount = in.readInt(); mIntent = in.readParcelable(Intent.class.getClassLoader()); mNotificationTimestamp = in.readLong(); mStatuses = new ArrayList<>(); @@ -385,6 +400,7 @@ public class PeopleSpaceTile implements Parcelable { dest.writeCharSequence(mNotificationContent); dest.writeString(mNotificationCategory); dest.writeParcelable(mNotificationDataUri, flags); + dest.writeInt(mMessagesCount); dest.writeParcelable(mIntent, flags); dest.writeLong(mNotificationTimestamp); dest.writeParcelableList(mStatuses, flags); diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java index c8fa5c8f28e2..c71badb0d484 100644 --- a/core/java/android/app/time/TimeManager.java +++ b/core/java/android/app/time/TimeManager.java @@ -264,7 +264,7 @@ public final class TimeManager { * See {@link ExternalTimeSuggestion} for more details. * {@hide} */ - @RequiresPermission(android.Manifest.permission.SET_TIME) + @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME) public void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) { if (DEBUG) { Log.d(TAG, "suggestExternalTime called: " + timeSuggestion); diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index 5d50c5d77887..ef921728c616 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -110,7 +110,9 @@ public final class UsageStats implements Parcelable { 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 @@ public final class UsageStats implements Parcelable { } /** - * 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 5af3b5a01d07..c04d3bef3af3 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -40,6 +40,7 @@ import java.io.IOException; 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 @@ public class AppSearchShortcutInfo extends GenericDocument { 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 cfb6e1b572aa..5a89708b5883 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -321,6 +321,10 @@ public class UserInfo implements Parcelable { 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 fa6472ee4a79..4674aa2d120e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -6030,7 +6030,7 @@ public abstract class BatteryStats implements Parcelable { 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 087568defb27..34f2c103f866 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -99,6 +99,8 @@ interface IUserManager { 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 b42a495ece56..219912c24e70 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -218,7 +218,7 @@ public class SystemVibrator extends Vibrator { @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 b528eb157e36..841aad556d6a 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -211,7 +211,7 @@ public class SystemVibratorManager extends VibratorManager { @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 5069e0319119..d4de4fa65526 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; 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 class UserManager { 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 @@ public class UserManager { } /** + * 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 @@ public class UserManager { } /** + * 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 @@ public class UserManager { } /** + * 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 217f1785bcd6..cec323f8b423 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -21,6 +21,8 @@ import android.annotation.NonNull; 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 @@ public final class VibrationAttributes implements Parcelable { 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 @@ public final class VibrationAttributes implements Parcelable { } } + @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 89478fac2f1a..6311760a4bfc 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 0199fad067cb..c78bf8c04c91 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; @@ -28,6 +29,11 @@ import android.content.Context; import android.hardware.vibrator.V1_0.EffectStrength; import android.hardware.vibrator.V1_3.Effect; import android.net.Uri; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.MathUtils; import com.android.internal.util.Preconditions; @@ -45,11 +51,6 @@ import java.util.Objects; * These effects may be any number of things, from single shot vibrations to complex waveforms. */ public abstract class VibrationEffect implements Parcelable { - private static final int PARCEL_TOKEN_ONE_SHOT = 1; - private static final int PARCEL_TOKEN_WAVEFORM = 2; - private static final int PARCEL_TOKEN_EFFECT = 3; - private static final int PARCEL_TOKEN_COMPOSITION = 4; - // Stevens' coefficient to scale the perceived vibration intensity. private static final float SCALE_GAMMA = 0.65f; @@ -181,9 +182,7 @@ public abstract class VibrationEffect implements Parcelable { * @return The desired effect. */ public static VibrationEffect createOneShot(long milliseconds, int amplitude) { - VibrationEffect effect = new OneShot(milliseconds, amplitude); - effect.validate(); - return effect; + return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */); } /** @@ -243,7 +242,19 @@ public abstract class VibrationEffect implements Parcelable { * @return The desired effect. */ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { - VibrationEffect effect = new Waveform(timings, amplitudes, repeat); + if (timings.length != amplitudes.length) { + throw new IllegalArgumentException( + "timing and amplitude arrays must be of equal length" + + " (timings.length=" + timings.length + + ", amplitudes.length=" + amplitudes.length + ")"); + } + List<StepSegment> segments = new ArrayList<>(); + for (int i = 0; i < timings.length; i++) { + float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE + ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; + segments.add(new StepSegment(parsedAmplitude, /* frequency= */ 0, (int) timings[i])); + } + VibrationEffect effect = new Composed(segments, repeat); effect.validate(); return effect; } @@ -317,7 +328,8 @@ public abstract class VibrationEffect implements Parcelable { */ @TestApi public static VibrationEffect get(int effectId, boolean fallback) { - VibrationEffect effect = new Prebaked(effectId, fallback, EffectStrength.MEDIUM); + VibrationEffect effect = new Composed( + new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM)); effect.validate(); return effect; } @@ -379,8 +391,26 @@ public abstract class VibrationEffect implements Parcelable { * @see VibrationEffect.Composition */ @NonNull - public static VibrationEffect.Composition startComposition() { - return new VibrationEffect.Composition(); + public static Composition startComposition() { + return new Composition(); + } + + /** + * Start building a waveform vibration. + * + * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing + * control over vibration frequency and ramping up or down the vibration amplitude, frequency or + * both. + * + * <p>For simpler waveform patterns see {@link #createWaveform} methods. + * + * @hide + * @see VibrationEffect.WaveformBuilder + */ + @TestApi + @NonNull + public static WaveformBuilder startWaveform() { + return new WaveformBuilder(); } @Override @@ -428,32 +458,28 @@ public abstract class VibrationEffect implements Parcelable { public abstract <T extends VibrationEffect> T scale(float scaleFactor); /** - * Scale given vibration intensity by the given factor. - * - * @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 + * Applies given effect strength to prebaked effects represented by one of + * VibrationEffect.EFFECT_*. * + * @param effectStrength new effect strength to be applied, one of + * VibrationEffect.EFFECT_STRENGTH_*. + * @return this if there is no change to this effect, or a copy of this effect with applied + * effect strength otherwise. * @hide */ - protected static int scale(int amplitude, float scaleFactor) { - if (amplitude == 0) { - return 0; - } - int scaled = (int) (scale((float) amplitude / MAX_AMPLITUDE, scaleFactor) * MAX_AMPLITUDE); - return MathUtils.constrain(scaled, 1, MAX_AMPLITUDE); + public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) { + return (T) this; } /** * Scale given vibration intensity by the given factor. * - * @param intensity relative intensity of the effect, must be between 0 and 1 + * @param intensity relative intensity of the effect, must be between 0 and 1 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will * scale down the intensity, values larger than 1 will scale up - * * @hide */ - protected static float scale(float intensity, float scaleFactor) { + public static float scale(float intensity, float scaleFactor) { // Applying gamma correction to the scale factor, which is the same as encoding the input // value, scaling it, then decoding the scaled value. float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA); @@ -516,545 +542,152 @@ public abstract class VibrationEffect implements Parcelable { } } - /** @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 OneShot(Parcel in) { - mDuration = in.readLong(); - mAmplitude = in.readInt(); - } + public static final class Composed extends VibrationEffect { + private final ArrayList<VibrationEffectSegment> mSegments; + private final int mRepeatIndex; - public OneShot(long milliseconds, int amplitude) { - mDuration = milliseconds; - mAmplitude = amplitude; + Composed(@NonNull Parcel in) { + this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt()); } - @Override - public long getDuration() { - return mDuration; - } - - public int getAmplitude() { - return mAmplitude; - } - - /** @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)); + Composed(@NonNull VibrationEffectSegment segment) { + this(Arrays.asList(segment), /* repeatIndex= */ -1); } /** @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; + public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) { + super(); + mSegments = new ArrayList<>(segments); + mRepeatIndex = repeatIndex; } - /** @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 long getDuration() { - if (mRepeat >= 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; - } - scaled = true; - scaledAmplitudes[i] = scale(scaledAmplitudes[i], scaleFactor); - } - if (!scaled) { - // Just return this if no scaling was done. - return this; - } - return new Waveform(mTimings, scaledAmplitudes, mRepeat); - } - - /** @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 + ")"); + 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 (!hasNonZeroEntry(mTimings)) { + if (!hasNonZeroDuration) { 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) + ")"); - } + + " (segments=" + mSegments + ")"); } - 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 (!hasNonZeroAmplitude) { + throw new IllegalArgumentException("at least one amplitude must be non-zero" + + " (segments=" + mSegments + ")"); } - 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 + ")"); + 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 boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.Waveform)) { - return false; + public long getDuration() { + if (mRepeatIndex >= 0) { + return Long.MAX_VALUE; } - 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; + 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; } + totalDuration += segmentDuration; } - 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; + return totalDuration; } - /** @hide */ + @NonNull @Override - public Prebaked resolve(int defaultAmplitude) { - if (mFallbackEffect != null) { - VibrationEffect resolvedFallback = mFallbackEffect.resolve(defaultAmplitude); - if (!mFallbackEffect.equals(resolvedFallback)) { - return new Prebaked(mEffectId, mEffectStrength, resolvedFallback); - } + 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)); } - return this; + if (resolvedSegments.equals(mSegments)) { + return this; + } + Composed resolved = new Composed(resolvedSegments, mRepeatIndex); + resolved.validate(); + return resolved; } - /** @hide */ + @NonNull @Override - public Prebaked scale(float scaleFactor) { - if (mFallbackEffect != null) { - VibrationEffect scaledFallback = mFallbackEffect.scale(scaleFactor); - if (!mFallbackEffect.equals(scaledFallback)) { - return new Prebaked(mEffectId, mEffectStrength, scaledFallback); - } + public Composed scale(float scaleFactor) { + int segmentCount = mSegments.size(); + ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount); + for (int i = 0; i < segmentCount; i++) { + scaledSegments.add(mSegments.get(i).scale(scaleFactor)); } - // 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; + if (scaledSegments.equals(mSegments)) { + return this; } + Composed scaled = new Composed(scaledSegments, mRepeatIndex); + scaled.validate(); + return scaled; } - /** @hide */ + @NonNull @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 + ")"); - } + 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 (!isValidEffectStrength(mEffectStrength)) { - throw new IllegalArgumentException( - "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); + if (scaledSegments.equals(mSegments)) { + return this; } + Composed scaled = new Composed(scaledSegments, mRepeatIndex); + scaled.validate(); + return scaled; } @Override public boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.Prebaked)) { + if (!(o instanceof Composed)) { return false; } - VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; - return mEffectId == other.mEffectId - && mFallback == other.mFallback - && mEffectStrength == other.mEffectStrength - && Objects.equals(mFallbackEffect, other.mFallbackEffect); + Composed other = (Composed) o; + return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex; } @Override public int hashCode() { - return Objects.hash(mEffectId, mFallback, mEffectStrength, mFallbackEffect); + return Objects.hash(mSegments, mRepeatIndex); } @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; - } - - /** @hide */ - @Override - public VibrationEffect resolve(int defaultAmplitude) { - // Primitive effects already have default primitive intensity set, so ignore this. - return this; - } - - /** @hide */ - @Override - public Composed scale(float scaleFactor) { - if (scaleFactor == 1f) { - // Just return this if there's no scaling to be done. - 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); - } - - /** @hide */ - @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"); - } - } - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_COMPOSITION); - out.writeList(mPrimitiveEffects); + return "Composed{segments=" + mSegments + + ", repeat=" + mRepeatIndex + + "}"; } @Override @@ -1063,34 +696,20 @@ public abstract class VibrationEffect implements Parcelable { } @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); - } - - @Override - public int hashCode() { - return Objects.hash(mPrimitiveEffects); - } - - @Override - public String toString() { - return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}'; + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeList(mSegments); + out.writeInt(mRepeatIndex); } - public static final @NonNull Parcelable.Creator<Composed> CREATOR = - new Parcelable.Creator<Composed>() { + @NonNull + public static final Creator<Composed> CREATOR = + new Creator<Composed>() { @Override - public Composed createFromParcel(@NonNull Parcel in) { - // Skip the type token - in.readInt(); + public Composed createFromParcel(Parcel in) { return new Composed(in); } @Override - @NonNull public Composed[] newArray(int size) { return new Composed[size]; } @@ -1115,7 +734,7 @@ public abstract class VibrationEffect implements Parcelable { PRIMITIVE_LOW_TICK, }) @Retention(RetentionPolicy.SOURCE) - public @interface Primitive {} + public @interface PrimitiveType {} /** * No haptic effect. Used to generate extended delays between primitives. @@ -1166,9 +785,46 @@ public abstract class VibrationEffect implements Parcelable { public static final int PRIMITIVE_LOW_TICK = 8; - private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>(); + private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); + private int mRepeatIndex = -1; + + Composition() {} - Composition() { } + /** + * Add a haptic effect to the end of the current composition. + * + * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied. + * + * @param effect The effect to add to this composition as a primitive + * @return The {@link Composition} object to enable adding multiple primitives in one chain. + * @hide + */ + @TestApi + @NonNull + public Composition addEffect(@NonNull VibrationEffect effect) { + return addEffect(effect, /* delay= */ 0); + } + + /** + * Add a haptic effect to the end of the current composition. + * + * @param effect The effect to add to this composition as a primitive + * @param delay The amount of time in milliseconds to wait before playing this primitive + * @return The {@link Composition} object to enable adding multiple primitives in one chain. + * @hide + */ + @TestApi + @NonNull + public Composition addEffect(@NonNull VibrationEffect effect, + @IntRange(from = 0) int delay) { + Preconditions.checkArgumentNonnegative(delay); + if (delay > 0) { + // Created a segment sustaining the zero amplitude to represent the delay. + addSegment(new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, + /* duration= */ delay)); + } + return addSegments(effect); + } /** * Add a haptic primitive to the end of the current composition. @@ -1181,9 +837,8 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId) { - addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); - return this; + public Composition addPrimitive(@PrimitiveType int primitiveId) { + return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); } /** @@ -1197,10 +852,9 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId, + public Composition addPrimitive(@PrimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale) { - addPrimitive(primitiveId, scale, /*delay*/ 0); - return this; + return addPrimitive(primitiveId, scale, /*delay*/ 0); } /** @@ -1213,9 +867,36 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId, + public Composition addPrimitive(@PrimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { - mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay)); + PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, + delay); + primitive.validate(); + return addSegment(primitive); + } + + private Composition addSegment(VibrationEffectSegment segment) { + if (mRepeatIndex >= 0) { + throw new IllegalStateException( + "Composition already have a repeating effect so any new primitive would be" + + " unreachable."); + } + mSegments.add(segment); + return this; + } + + private Composition addSegments(VibrationEffect effect) { + if (mRepeatIndex >= 0) { + throw new IllegalStateException( + "Composition already have a repeating effect so any new primitive would be" + + " unreachable."); + } + Composed composed = (Composed) effect; + if (composed.getRepeatIndex() >= 0) { + // Start repeating from the index relative to the composed waveform. + mRepeatIndex = mSegments.size() + composed.getRepeatIndex(); + } + mSegments.addAll(composed.getSegments()); return this; } @@ -1230,22 +911,13 @@ public abstract class VibrationEffect implements Parcelable { */ @NonNull public VibrationEffect compose() { - if (mEffects.isEmpty()) { + if (mSegments.isEmpty()) { throw new IllegalStateException( "Composition must have at least one element to compose."); } - return new VibrationEffect.Composed(mEffects); - } - - /** - * @throws IllegalArgumentException throws if the primitive ID is not within the valid range - * @hide - * - */ - static int checkPrimitive(int primitiveId) { - Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LOW_TICK, - "primitiveId"); - return primitiveId; + VibrationEffect effect = new Composed(mSegments, mRepeatIndex); + effect.validate(); + return effect; } /** @@ -1254,7 +926,7 @@ public abstract class VibrationEffect implements Parcelable { * @return The ID in a human readable format. * @hide */ - public static String primitiveToString(@Primitive int id) { + public static String primitiveToString(@PrimitiveType int id) { switch (id) { case PRIMITIVE_NOOP: return "PRIMITIVE_NOOP"; @@ -1278,90 +950,172 @@ public abstract class VibrationEffect implements Parcelable { return Integer.toString(id); } } + } + + /** + * A builder for waveform haptic effects. + * + * <p>Waveform vibrations constitute of one or more timed segments where the vibration + * amplitude, frequency or both can linearly ramp to new values. + * + * <p>Waveform segments may have zero duration, which represent a jump to new vibration + * amplitude and/or frequency values. + * + * <p>Waveform segments may have the same start and end vibration amplitude and frequency, + * which represent a step where the amplitude and frequency are maintained for that duration. + * + * @hide + * @see VibrationEffect#startWaveform() + */ + @TestApi + public static final class WaveformBuilder { + private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); + WaveformBuilder() {} /** - * @hide + * Vibrate with given amplitude for the given duration, in millis, keeping the previous + * frequency the same. + * + * <p>If the duration is zero the vibrator will jump to new amplitude. + * + * @param amplitude The amplitude for this step + * @param duration The duration of this step in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. */ - public static class PrimitiveEffect implements Parcelable { - public int id; - public float scale; - public int delay; - - PrimitiveEffect(int id, float scale, int delay) { - this.id = id; - this.scale = scale; - this.delay = delay; - } + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, + @IntRange(from = 0) int duration) { + return addStep(amplitude, getPreviousFrequency(), duration); + } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(id); - dest.writeFloat(scale); - dest.writeInt(delay); - } + /** + * Vibrate with given amplitude for the given duration, in millis, keeping the previous + * vibration frequency the same. + * + * <p>If the duration is zero the vibrator will jump to new amplitude. + * + * @param amplitude The amplitude for this step + * @param frequency The frequency for this step + * @param duration The duration of this step in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, + @FloatRange(from = -1f, to = 1f) float frequency, + @IntRange(from = 0) int duration) { + mSegments.add(new StepSegment(amplitude, frequency, duration)); + return this; + } - @Override - public int describeContents() { - return 0; - } + /** + * Ramp vibration linearly for the given duration, in millis, from previous amplitude value + * to the given one, keeping previous frequency. + * + * <p>If the duration is zero the vibrator will jump to new amplitude. + * + * @param amplitude The final amplitude this ramp should reach + * @param duration The duration of this ramp in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, + @IntRange(from = 0) int duration) { + return addRamp(amplitude, getPreviousFrequency(), duration); + } - @Override - public String toString() { - return "PrimitiveEffect{" - + "id=" + primitiveToString(id) - + ", scale=" + scale - + ", delay=" + delay - + '}'; - } + /** + * Ramp vibration linearly for the given duration, in millis, from previous amplitude and + * frequency values to the given ones. + * + * <p>If the duration is zero the vibrator will jump to new amplitude and frequency. + * + * @param amplitude The final amplitude this ramp should reach + * @param frequency The final frequency this ramp should reach + * @param duration The duration of this ramp in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, + @FloatRange(from = -1f, to = 1f) float frequency, + @IntRange(from = 0) int duration) { + mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude, getPreviousFrequency(), + frequency, duration)); + return this; + } - @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; - } + /** + * Compose all of the steps together into a single {@link VibrationEffect}. + * + * The {@link WaveformBuilder} object is still valid after this call, so you can + * continue adding more primitives to it and generating more {@link VibrationEffect}s by + * calling this method again. + * + * @return The {@link VibrationEffect} resulting from the composition of the steps. + */ + @NonNull + public VibrationEffect build() { + return build(/* repeat= */ -1); + } - @Override - public int hashCode() { - return Objects.hash(id, scale, delay); + /** + * Compose all of the steps together into a single {@link VibrationEffect}. + * + * <p>To cause the pattern to repeat, pass the index at which to start the repetition + * (starting at 0), or -1 to disable repeating. + * + * <p>The {@link WaveformBuilder} object is still valid after this call, so you can + * continue adding more primitives to it and generating more {@link VibrationEffect}s by + * calling this method again. + * + * @return The {@link VibrationEffect} resulting from the composition of the steps. + */ + @NonNull + public VibrationEffect build(int repeat) { + if (mSegments.isEmpty()) { + throw new IllegalStateException( + "WaveformBuilder must have at least one element to build."); } + VibrationEffect effect = new Composed(mSegments, repeat); + effect.validate(); + return effect; + } + + private float getPreviousFrequency() { + if (!mSegments.isEmpty()) { + VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); + if (segment instanceof StepSegment) { + return ((StepSegment) segment).getFrequency(); + } else if (segment instanceof RampSegment) { + return ((RampSegment) segment).getEndFrequency(); + } + } + return 0; + } - - 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]; - } - }; + private float getPreviousAmplitude() { + if (!mSegments.isEmpty()) { + VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); + if (segment instanceof StepSegment) { + return ((StepSegment) segment).getAmplitude(); + } else if (segment instanceof RampSegment) { + return ((RampSegment) segment).getEndAmplitude(); + } + } + return 0; } } - public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR = + @NonNull + public static final Parcelable.Creator<VibrationEffect> CREATOR = new Parcelable.Creator<VibrationEffect>() { @Override public VibrationEffect createFromParcel(Parcel in) { - int token = in.readInt(); - if (token == PARCEL_TOKEN_ONE_SHOT) { - return new OneShot(in); - } else if (token == PARCEL_TOKEN_WAVEFORM) { - return new Waveform(in); - } else if (token == PARCEL_TOKEN_EFFECT) { - return new Prebaked(in); - } else if (token == PARCEL_TOKEN_COMPOSITION) { - return new Composed(in); - } else { - throw new IllegalStateException( - "Unexpected vibration event type token in parcel."); - } + return new Composed(in); } @Override public VibrationEffect[] newArray(int size) { diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index b90d438ffb93..a0f70c8fa526 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -467,7 +467,7 @@ public abstract class Vibrator { */ @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 @@ public abstract class Vibrator { * @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 3121b952281e..9c46bc9eb149 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -153,7 +153,8 @@ public final class VibratorInfo implements Parcelable { * @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/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index b5abe2a3e311..36177c46ebef 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -16,6 +16,8 @@ package android.os.storage; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -289,9 +291,14 @@ public final class StorageVolume implements Parcelable { return mMaxFileSize; } - /** {@hide} */ + /** + * Returns the user that owns this volume + * + * {@hide} + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public UserHandle getOwner() { + @SystemApi(client = MODULE_LIBRARIES) + public @NonNull UserHandle getOwner() { return mOwner; } diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java new file mode 100644 index 000000000000..78b43468d663 --- /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 000000000000..2ef29cb26ebc --- /dev/null +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that plays a primitive vibration effect after a + * specified delay and applying a given scale. + * + * @hide + */ +@TestApi +public final class PrimitiveSegment extends VibrationEffectSegment { + private final int mPrimitiveId; + private final float mScale; + private final int mDelay; + + PrimitiveSegment(@NonNull Parcel in) { + this(in.readInt(), in.readFloat(), in.readInt()); + } + + /** @hide */ + public PrimitiveSegment(int id, float scale, int delay) { + mPrimitiveId = id; + mScale = scale; + mDelay = delay; + } + + public int getPrimitiveId() { + return mPrimitiveId; + } + + public float getScale() { + return mScale; + } + + public int getDelay() { + return mDelay; + } + + @Override + public long getDuration() { + return -1; + } + + @Override + public boolean hasNonZeroAmplitude() { + // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0. + return true; + } + + @NonNull + @Override + public PrimitiveSegment resolve(int defaultAmplitude) { + return this; + } + + @NonNull + @Override + public PrimitiveSegment scale(float scaleFactor) { + return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor), + mDelay); + } + + @NonNull + @Override + public PrimitiveSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public void validate() { + Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP, + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId"); + Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale"); + Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0"); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(PARCEL_TOKEN_PRIMITIVE); + dest.writeInt(mPrimitiveId); + dest.writeFloat(mScale); + dest.writeInt(mDelay); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "Primitive{" + + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId) + + ", scale=" + mScale + + ", delay=" + mDelay + + '}'; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PrimitiveSegment that = (PrimitiveSegment) o; + return mPrimitiveId == that.mPrimitiveId + && Float.compare(that.mScale, mScale) == 0 + && mDelay == that.mDelay; + } + + @Override + public int hashCode() { + return Objects.hash(mPrimitiveId, mScale, mDelay); + } + + @NonNull + public static final Parcelable.Creator<PrimitiveSegment> CREATOR = + new Parcelable.Creator<PrimitiveSegment>() { + @Override + public PrimitiveSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new PrimitiveSegment(in); + } + + @Override + public PrimitiveSegment[] newArray(int size) { + return new PrimitiveSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java new file mode 100644 index 000000000000..aad87c5cd4e7 --- /dev/null +++ b/core/java/android/os/vibrator/RampSegment.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency + * for a specified duration. + * + * @hide + */ +@TestApi +public final class RampSegment extends VibrationEffectSegment { + private final float mStartAmplitude; + private final float mStartFrequency; + private final float mEndAmplitude; + private final float mEndFrequency; + private final int mDuration; + + RampSegment(@NonNull Parcel in) { + this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt()); + } + + /** @hide */ + public RampSegment(float startAmplitude, float endAmplitude, float startFrequency, + float endFrequency, int duration) { + mStartAmplitude = startAmplitude; + mEndAmplitude = endAmplitude; + mStartFrequency = startFrequency; + mEndFrequency = endFrequency; + mDuration = duration; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RampSegment)) { + return false; + } + RampSegment other = (RampSegment) o; + return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0 + && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0 + && Float.compare(mStartFrequency, other.mStartFrequency) == 0 + && Float.compare(mEndFrequency, other.mEndFrequency) == 0 + && mDuration == other.mDuration; + } + + public float getStartAmplitude() { + return mStartAmplitude; + } + + public float getEndAmplitude() { + return mEndAmplitude; + } + + public float getStartFrequency() { + return mStartFrequency; + } + + public float getEndFrequency() { + return mEndFrequency; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public boolean hasNonZeroAmplitude() { + return mStartAmplitude > 0 || mEndAmplitude > 0; + } + + @Override + public void validate() { + Preconditions.checkArgumentNonnegative(mDuration, + "Durations must all be >= 0, got " + mDuration); + Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude"); + Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude"); + } + + + @NonNull + @Override + public RampSegment resolve(int defaultAmplitude) { + // Default amplitude is not supported for ramping. + return this; + } + + @NonNull + @Override + public RampSegment scale(float scaleFactor) { + float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor); + float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor); + if (Float.compare(mStartAmplitude, newStartAmplitude) == 0 + && Float.compare(mEndAmplitude, newEndAmplitude) == 0) { + return this; + } + return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequency, mEndFrequency, + mDuration); + } + + @NonNull + @Override + public RampSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequency, mEndFrequency, + mDuration); + } + + @Override + public String toString() { + return "Ramp{startAmplitude=" + mStartAmplitude + + ", endAmplitude=" + mEndAmplitude + + ", startFrequency=" + mStartFrequency + + ", endFrequency=" + mEndFrequency + + ", duration=" + mDuration + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_RAMP); + out.writeFloat(mStartAmplitude); + out.writeFloat(mEndAmplitude); + out.writeFloat(mStartFrequency); + out.writeFloat(mEndFrequency); + out.writeInt(mDuration); + } + + @NonNull + public static final Creator<RampSegment> CREATOR = + new Creator<RampSegment>() { + @Override + public RampSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new RampSegment(in); + } + + @Override + public RampSegment[] newArray(int size) { + return new RampSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java new file mode 100644 index 000000000000..11209e0ee425 --- /dev/null +++ b/core/java/android/os/vibrator/StepSegment.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude and + * frequency for a specified duration. + * + * @hide + */ +@TestApi +public final class StepSegment extends VibrationEffectSegment { + private final float mAmplitude; + private final float mFrequency; + private final int mDuration; + + StepSegment(@NonNull Parcel in) { + this(in.readFloat(), in.readFloat(), in.readInt()); + } + + /** @hide */ + public StepSegment(float amplitude, float frequency, int duration) { + mAmplitude = amplitude; + mFrequency = frequency; + mDuration = duration; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof StepSegment)) { + return false; + } + StepSegment other = (StepSegment) o; + return Float.compare(mAmplitude, other.mAmplitude) == 0 + && Float.compare(mFrequency, other.mFrequency) == 0 + && mDuration == other.mDuration; + } + + public float getAmplitude() { + return mAmplitude; + } + + public float getFrequency() { + return mFrequency; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public boolean hasNonZeroAmplitude() { + // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later. + return Float.compare(mAmplitude, 0) != 0; + } + + @Override + public void validate() { + Preconditions.checkArgumentNonnegative(mDuration, + "Durations must all be >= 0, got " + mDuration); + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { + Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude"); + } + } + + @NonNull + @Override + public StepSegment resolve(int defaultAmplitude) { + if (defaultAmplitude > VibrationEffect.MAX_AMPLITUDE || defaultAmplitude <= 0) { + throw new IllegalArgumentException( + "amplitude must be between 1 and 255 inclusive (amplitude=" + + defaultAmplitude + ")"); + } + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { + return this; + } + return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mFrequency, + mDuration); + } + + @NonNull + @Override + public StepSegment scale(float scaleFactor) { + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { + return this; + } + return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequency, + mDuration); + } + + @NonNull + @Override + public StepSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(mAmplitude, mFrequency, mDuration); + } + + @Override + public String toString() { + return "Step{amplitude=" + mAmplitude + + ", frequency=" + mFrequency + + ", duration=" + mDuration + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_STEP); + out.writeFloat(mAmplitude); + out.writeFloat(mFrequency); + out.writeInt(mDuration); + } + + @NonNull + public static final Parcelable.Creator<StepSegment> CREATOR = + new Parcelable.Creator<StepSegment>() { + @Override + public StepSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new StepSegment(in); + } + + @Override + public StepSegment[] newArray(int size) { + return new StepSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java new file mode 100644 index 000000000000..5b42845cfa43 --- /dev/null +++ b/core/java/android/os/vibrator/VibrationEffectSegment.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +/** + * Representation of a single segment of a {@link VibrationEffect}. + * + * <p>Vibration effects are represented as a sequence of segments that describes how vibration + * amplitude and frequency changes over time. Segments can be described as one of the following: + * + * <ol> + * <li>A predefined vibration effect; + * <li>A composable effect primitive; + * <li>Fixed amplitude and frequency values to be held for a specified duration; + * <li>Pairs of amplitude and frequency values to be ramped to for a specified duration; + * </ol> + * + * @hide + */ +@TestApi +@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here. +public abstract class VibrationEffectSegment implements Parcelable { + static final int PARCEL_TOKEN_PREBAKED = 1; + static final int PARCEL_TOKEN_PRIMITIVE = 2; + static final int PARCEL_TOKEN_STEP = 3; + static final int PARCEL_TOKEN_RAMP = 4; + + /** Prevent subclassing from outside of this package */ + VibrationEffectSegment() { + } + + /** + * Gets the estimated duration of the segment in milliseconds. + * + * <p>For segments with an unknown duration (e.g. prebaked or primitive effects where the length + * is device and potentially run-time dependent), this returns -1. + */ + public abstract long getDuration(); + + /** Returns true if this segment plays at a non-zero amplitude at some point. */ + public abstract boolean hasNonZeroAmplitude(); + + /** Validates the segment, throwing exceptions if any parameter is invalid. */ + public abstract void validate(); + + /** + * Resolves amplitudes set to {@link VibrationEffect#DEFAULT_AMPLITUDE}. + * + * <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger + * than {@link VibrationEffect#MAX_AMPLITUDE}. + */ + @NonNull + public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude); + + /** + * Scale the segment intensity with the given factor. + * + * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will + * scale down the intensity, values larger than 1 will scale up + */ + @NonNull + public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor); + + /** + * Applies given effect strength to prebaked effects. + * + * @param effectStrength new effect strength to be applied, one of + * VibrationEffect.EFFECT_STRENGTH_*. + */ + @NonNull + public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength); + + @NonNull + public static final Creator<VibrationEffectSegment> CREATOR = + new Creator<VibrationEffectSegment>() { + @Override + public VibrationEffectSegment createFromParcel(Parcel in) { + switch (in.readInt()) { + case PARCEL_TOKEN_STEP: + return new StepSegment(in); + case PARCEL_TOKEN_RAMP: + return new RampSegment(in); + case PARCEL_TOKEN_PREBAKED: + return new PrebakedSegment(in); + case PARCEL_TOKEN_PRIMITIVE: + return new PrimitiveSegment(in); + default: + throw new IllegalStateException( + "Unexpected vibration event type token in parcel."); + } + } + + @Override + public VibrationEffectSegment[] newArray(int size) { + return new VibrationEffectSegment[size]; + } + }; +} diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 80a3e1693ab1..921911bbf479 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -283,9 +283,7 @@ public class PermissionUsageHelper { continue; } - if (packageName.equals(SYSTEM_PKG) - || (!shouldShowPermissionsHub() - && !isUserSensitive(packageName, user, op))) { + if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) { continue; } @@ -372,8 +370,10 @@ public class PermissionUsageHelper { proxyLabels.put(usage, new ArrayList<>()); proxyUids.add(usage.uid); } - if (!mostRecentUsages.containsKey(usage.uid) || usage.lastAccessTime - > mostRecentUsages.get(usage.uid).lastAccessTime) { + // If this usage is not by the system, and is more recent than the next-most recent + // for it's uid, save it. + if (!usage.packageName.equals(SYSTEM_PKG) && (!mostRecentUsages.containsKey(usage.uid) + || usage.lastAccessTime > mostRecentUsages.get(usage.uid).lastAccessTime)) { mostRecentUsages.put(usage.uid, usage); } } @@ -416,20 +416,22 @@ public class PermissionUsageHelper { } proxyUids.add(currentUsage.uid); - try { - PackageManager userPkgManager = - getUserContext(currentUsage.getUser()).getPackageManager(); - ApplicationInfo appInfo = userPkgManager.getApplicationInfo( - currentUsage.packageName, 0); - CharSequence appLabel = appInfo.loadLabel(userPkgManager); - // If we don't already have the app label, and it's not the same as the main - // app, add it - if (!proxyLabelList.contains(appLabel) - && !currentUsage.packageName.equals(start.packageName)) { - proxyLabelList.add(appLabel); + // Don't add an app label for the main app, or the system app + if (!currentUsage.packageName.equals(start.packageName) + && !currentUsage.packageName.equals(SYSTEM_PKG)) { + try { + PackageManager userPkgManager = + getUserContext(currentUsage.getUser()).getPackageManager(); + ApplicationInfo appInfo = userPkgManager.getApplicationInfo( + currentUsage.packageName, 0); + CharSequence appLabel = appInfo.loadLabel(userPkgManager); + // If we don't already have the app label add it + if (!proxyLabelList.contains(appLabel)) { + proxyLabelList.add(appLabel); + } + } catch (PackageManager.NameNotFoundException e) { + // Ignore } - } catch (PackageManager.NameNotFoundException e) { - // Ignore } iterNum++; } diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 374de9ccb2f4..286db9e18f83 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4574,6 +4574,13 @@ public final class Telephony { * This is the same as {@link ServiceState#getIsManualSelection()}. */ public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection"; + + /** + * The current data network type. + * <p> + * This is the same as {@link TelephonyManager#getDataNetworkType()}. + */ + public static final String DATA_NETWORK_TYPE = "data_network_type"; } /** diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl index 8d798c345295..297f00a4d753 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 @@ import com.android.internal.os.IResultReceiver; 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 c5f1f04caaab..7edf2e23a528 100644 --- a/core/java/android/service/translation/TranslationService.java +++ b/core/java/android/service/translation/TranslationService.java @@ -34,8 +34,12 @@ import android.os.Handler; 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 android.view.translation.TranslationSpec; import com.android.internal.os.IResultReceiver; +import java.util.Set; +import java.util.function.Consumer; + /** * Service for translating text. * @hide @@ -92,10 +99,20 @@ public abstract class TranslationService extends Service { } @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 @@ public abstract class TranslationService extends Service { /** * 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 @@ public abstract class TranslationService extends Service { @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 @@ public abstract class TranslationService extends Service { } 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 d3eaeae993e6..2a43222b9e48 100644 --- a/core/java/android/util/Slog.java +++ b/core/java/android/util/Slog.java @@ -26,6 +26,10 @@ import java.util.Formatter; 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 @@ public final class Slog { /** * 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 @@ public final class Slog { /** * 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 @@ public final class Slog { /** * 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 @@ public final class Slog { /** * 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 @@ public final class Slog { /** * 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 @@ public final class Slog { /** * 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 @@ public final class Slog { /** * 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 000000000000..dc93a473fe44 --- /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 b28cfb87e28d..2fcaec91833d 100644 --- a/core/java/android/util/imetracing/ImeTracing.java +++ b/core/java/android/util/imetracing/ImeTracing.java @@ -130,6 +130,16 @@ public abstract class ImeTracing { 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 @@ public abstract class ImeTracing { } /** - * 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 class ImeTracing { */ 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 35a81b7aeea5..17cdc4687660 100644 --- a/core/java/android/util/imetracing/ImeTracingClientImpl.java +++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java @@ -99,18 +99,10 @@ class ImeTracingClientImpl extends ImeTracing { } @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 77f017a4654a..06e4c5002776 100644 --- a/core/java/android/util/imetracing/ImeTracingServerImpl.java +++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java @@ -139,14 +139,6 @@ class ImeTracingServerImpl extends ImeTracing { } } - @GuardedBy("mEnabledLock") - @Override - public void writeTracesToFiles() { - synchronized (mEnabledLock) { - writeTracesToFilesLocked(); - } - } - private void writeTracesToFilesLocked() { try { ProtoOutputStream clientsProto = new ProtoOutputStream(); @@ -192,12 +184,6 @@ class ImeTracingServerImpl extends ImeTracing { @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 @@ class ImeTracingServerImpl extends ImeTracing { + 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/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java index 05636de8e8e4..bd20f5bf82fd 100644 --- a/core/java/android/view/NotificationTopLineView.java +++ b/core/java/android/view/NotificationTopLineView.java @@ -26,6 +26,9 @@ import android.widget.RemoteViews; 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 @@ import com.android.internal.R; */ @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 @@ public class NotificationTopLineView extends ViewGroup { 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 @@ public class NotificationTopLineView extends ViewGroup { 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 @@ public class NotificationTopLineView extends ViewGroup { 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 @@ public class NotificationTopLineView extends ViewGroup { } 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 @@ public class NotificationTopLineView extends ViewGroup { 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 @@ public class NotificationTopLineView extends ViewGroup { } 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 3ffe0c660f9a..f1eef9fad8a1 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1296,7 +1296,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 09a42cae52f1..dbccf10822cd 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -330,7 +330,6 @@ public final class ViewRootImpl implements ViewParent, private boolean mUseBLASTAdapter; private boolean mForceDisableBLAST; - private boolean mEnableTripleBuffering; private boolean mFastScrollSoundEffectsEnabled; @@ -1180,9 +1179,6 @@ public final class ViewRootImpl implements ViewParent, 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 = @@ -1374,17 +1370,10 @@ public final class ViewRootImpl implements ViewParent, // 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(); } @@ -1908,7 +1897,7 @@ public final class ViewRootImpl implements ViewParent, 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(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 04512c9abc0a..9df87dc79405 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2218,26 +2218,6 @@ public interface WindowManager extends ViewManager { 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 @@ public interface WindowManager extends ViewManager { * @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 @@ public interface WindowManager extends ViewManager { @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 47ac1ee5b339..18013e815d13 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -118,7 +118,6 @@ public final class WindowManagerGlobal { 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 37220fe6870b..c5bce28fcee1 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -608,7 +608,12 @@ public class BaseInputConnection implements InputConnection { 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 34a60bbe7642..38019c9a34f8 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -325,16 +325,16 @@ public interface InputConnection { 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 d347f31eb934..dbc32e9cf0ca 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -18,7 +18,9 @@ package android.view.translation; 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 @@ import java.util.List; * {@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/core/java/android/view/translation/TranslationCapability.aidl b/core/java/android/view/translation/TranslationCapability.aidl new file mode 100644 index 000000000000..3a80b17e401a --- /dev/null +++ b/core/java/android/view/translation/TranslationCapability.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +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 000000000000..28b21138eef1 --- /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/core/java/android/view/translation/TranslationContext.aidl b/core/java/android/view/translation/TranslationContext.aidl new file mode 100644 index 000000000000..cb6c23f0550b --- /dev/null +++ b/core/java/android/view/translation/TranslationContext.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +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 000000000000..1d3d182ecdf6 --- /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 6554e1a1db54..66b45f3e41e0 100644 --- a/core/java/android/view/translation/TranslationManager.java +++ b/core/java/android/view/translation/TranslationManager.java @@ -21,24 +21,27 @@ import android.annotation.Nullable; 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 @@ public final class TranslationManager { 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 final class TranslationManager { */ 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 @@ public final class TranslationManager { @NonNull @GuardedBy("mLock") - private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Integer> mTranslatorIds = + private final ArrayMap<TranslationContext, Integer> mTranslatorIds = new ArrayMap<>(); @NonNull @@ -110,23 +124,20 @@ public final class TranslationManager { * * <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 @@ public final class TranslationManager { 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 @@ public final class TranslationManager { 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 @@ public final class TranslationManager { } /** - * 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 0cc26e128a55..6b26e0698d12 100644 --- a/core/java/android/view/translation/Translator.java +++ b/core/java/android/view/translation/Translator.java @@ -44,7 +44,7 @@ import java.util.concurrent.TimeUnit; 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 @@ public class Translator { 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 @@ public class Translator { * @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 @@ public class Translator { */ 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 @@ public class Translator { /** @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 7f934c02228e..d79ecca1426e 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -376,15 +376,17 @@ public class UiTranslationController { } 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/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 3709aa1583ba..9789d70c09c4 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.graphics.drawable.Drawable; 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 @@ public final class SplashScreenView extends FrameLayout { * 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 @@ public final class SplashScreenView extends FrameLayout { 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 @@ public final class SplashScreenView extends FrameLayout { + 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 02cbccc77c3f..a5b894dff46d 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 @@ public final class SystemUiDeviceConfigFlags { // 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 7f8788529714..a043756ca262 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.PrintWriterPrinter; 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 @@ public class BatteryStatsImpl extends BatteryStats { } /** - * 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 f42d5da30b19..8ff3c106d229 100644 --- a/core/java/com/android/internal/widget/MediaNotificationView.java +++ b/core/java/com/android/internal/widget/MediaNotificationView.java @@ -19,31 +19,19 @@ package com.android.internal.widget; 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 @@ public class MediaNotificationView extends FrameLayout { 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 f49a834a96e0..f1fa5dbcbbdc 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -238,6 +238,7 @@ cc_library_shared { "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 b790056fb58e..b46b5a23e3fb 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -68,7 +68,7 @@ private: }; 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 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfac 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 void nativeSetTransactionCompleteCallback(JNIEnv* env, jclass clazz, jlon 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 52d21a858d4f..10927b9e566e 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 <input/InputTransport.h> #include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include <utils/Looper.h> -#include <input/InputTransport.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 @@ private: 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 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { 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,46 +213,56 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { 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; + const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks); + if (!notified) { + skipCallbacks = true; } + } +} - 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()); - } +/** + * 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 (!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; - } - } + 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(senderObj.get(), - gInputEventSenderClassInfo.dispatchInputEventFinished, - static_cast<jint>(seq), static_cast<jboolean>(result->handled)); - if (env->ExceptionCheck()) { - ALOGE("Exception dispatching finished signal."); - skipCallbacks = 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) { std::shared_ptr<InputChannel> inputChannel = diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto index bbb0edd4fd59..4f3ae28bec7d 100644 --- a/core/proto/android/server/biometrics.proto +++ b/core/proto/android/server/biometrics.proto @@ -178,4 +178,6 @@ enum ClientMonitorEnum { 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 aab054f4bf73..7b97524d0510 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -21,40 +21,49 @@ option java_multiple_files = true; 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 StepSegmentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 duration = 1; + optional float amplitude = 2; + optional float frequency = 3; } -message WaveformProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 timings = 1; - repeated int32 amplitudes = 2; - required bool repeat = 3; +message RampSegmentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 duration = 1; + optional float startAmplitude = 2; + optional float endAmplitude = 3; + optional float startFrequency = 4; + optional float endFrequency = 5; } -message 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; + optional int32 primitive_id = 1; + optional float scale = 2; + optional int32 delay = 3; +} + +message SegmentProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 effect_ids = 1; - repeated float effect_scales = 2; - repeated int32 delays = 3; + optional PrebakedSegmentProto prebaked = 1; + optional PrimitiveSegmentProto primitive = 2; + optional StepSegmentProto step = 3; + optional RampSegmentProto ramp = 4; } // A com.android.os.VibrationEffect object. message VibrationEffectProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional OneShotProto oneshot = 1; - optional WaveformProto waveform = 2; - optional PrebakedProto prebaked = 3; - optional ComposedProto composed = 4; + optional SegmentProto segments = 1; + required int32 repeat = 2; } message SyncVibrationEffectProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 29fa70de43a2..5aaf1fcafe1b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1388,6 +1388,14 @@ android:backgroundPermission="android.permission.BACKGROUND_CAMERA" android:protectionLevel="dangerous|instant" /> + <!-- Required to be able to discover and connect to nearby Bluetooth devices. + <p>Protection level: dangerous --> + <permission-group android:name="android.permission-group.NEARBY_DEVICES" + android:icon="@drawable/ic_qs_bluetooth" + android:label="@string/permgrouplab_nearby_devices" + android:description="@string/permgroupdesc_nearby_devices" + android:priority="750" /> + <!-- @SystemApi @TestApi Required to be able to access the camera device in the background. This permission is not intended to be held by apps. <p>Protection level: internal @@ -1930,6 +1938,22 @@ android:label="@string/permlab_bluetooth" android:protectionLevel="normal" /> + <!-- Required to be able to discover and pair nearby Bluetooth devices. + <p>Protection level: dangerous --> + <permission android:name="android.permission.BLUETOOTH_SCAN" + android:permissionGroup="android.permission-group.UNDEFINED" + android:description="@string/permdesc_bluetooth_scan" + android:label="@string/permlab_bluetooth_scan" + android:protectionLevel="dangerous" /> + + <!-- Required to be able to connect to paired Bluetooth devices. + <p>Protection level: dangerous --> + <permission android:name="android.permission.BLUETOOTH_CONNECT" + android:permissionGroup="android.permission-group.UNDEFINED" + android:description="@string/permdesc_bluetooth_connect" + android:label="@string/permlab_bluetooth_connect" + android:protectionLevel="dangerous" /> + <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the user from using them until they are unsuspended. @hide @@ -2939,6 +2963,15 @@ <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE" android:protectionLevel="signature" /> + <!-- Allows system clock time suggestions from an external clock / time source to be made. + The nature of "external" could be highly form-factor specific. Example, times + obtained via the VHAL for Android Auto OS. + <p>Not for use by third-party applications. + @SystemApi @hide + --> + <permission android:name="android.permission.SUGGEST_EXTERNAL_TIME" + android:protectionLevel="signature|privileged" /> + <!-- Allows applications like settings to manage configuration associated with automatic time and time zone detection. <p>Not for use by third-party applications. @@ -3554,7 +3587,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. @@ -4911,6 +4944,11 @@ <permission android:name="android.permission.SET_INITIAL_LOCK" android:protectionLevel="signature|setup"/> + <!-- @TestApi Allows applications to set and verify lockscreen credentials. + @hide --> + <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" + android:protectionLevel="signature"/> + <!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide --> <permission android:name="android.permission.MANAGE_FINGERPRINT" android:protectionLevel="signature|privileged" /> diff --git a/core/res/res/drawable/bottomsheet_background.xml b/core/res/res/drawable/bottomsheet_background.xml index 3a8ad71187a0..00d53004c9dc 100644 --- a/core/res/res/drawable/bottomsheet_background.xml +++ b/core/res/res/drawable/bottomsheet_background.xml @@ -18,5 +18,5 @@ <corners android:topLeftRadius="@dimen/config_bottomDialogCornerRadius" android:topRightRadius="@dimen/config_bottomDialogCornerRadius"/> - <solid android:color="?attr/colorBackgroundFloating" /> + <solid android:color="?attr/colorBackground" /> </shape> diff --git a/core/res/res/drawable/chooser_action_button_bg.xml b/core/res/res/drawable/chooser_action_button_bg.xml index 0dd9e9c7cd98..18bbd93d1535 100644 --- a/core/res/res/drawable/chooser_action_button_bg.xml +++ b/core/res/res/drawable/chooser_action_button_bg.xml @@ -15,7 +15,7 @@ ~ limitations under the License --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/lighter_gray"> + android:color="?android:attr/colorControlHighlight"> <item> <inset android:insetLeft="0dp" @@ -23,10 +23,8 @@ android:insetRight="0dp" android:insetBottom="8dp"> <shape android:shape="rectangle"> - <corners android:radius="16dp"></corners> - <stroke android:width="1dp" - android:color="?attr/opacityListDivider" /> - <solid android:color="?attr/colorBackgroundFloating" /> + <corners android:radius="16dp" /> + <solid android:color="@color/system_neutral2_100" /> </shape> </inset> </item> diff --git a/core/res/res/layout/chooser_action_button.xml b/core/res/res/layout/chooser_action_button.xml index 6af7937960f0..16ffaa4b90ca 100644 --- a/core/res/res/layout/chooser_action_button.xml +++ b/core/res/res/layout/chooser_action_button.xml @@ -25,6 +25,7 @@ android:singleLine="true" android:clickable="true" android:background="@drawable/chooser_action_button_bg" - android:drawableTint="@color/chooser_chip_icon" + android:drawableTint="?android:textColorPrimary" android:drawableTintMode="src_in" + style="?android:attr/borderlessButtonStyle" /> diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index c0de6936dd12..10683b189fc5 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -67,7 +67,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" @@ -84,7 +84,7 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <FrameLayout android:id="@android:id/tabcontent" diff --git a/core/res/res/layout/chooser_grid_preview_file.xml b/core/res/res/layout/chooser_grid_preview_file.xml index 2a39215a4bd8..d8c1d1782834 100644 --- a/core/res/res/layout/chooser_grid_preview_file.xml +++ b/core/res/res/layout/chooser_grid_preview_file.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="@dimen/chooser_view_spacing" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:layout_width="@dimen/chooser_preview_width" diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml index 62df1650612a..0d04d7f319b8 100644 --- a/core/res/res/layout/chooser_grid_preview_image.xml +++ b/core/res/res/layout/chooser_grid_preview_image.xml @@ -22,14 +22,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <RelativeLayout android:id="@+id/content_preview_image_area" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:paddingBottom="@dimen/chooser_view_spacing" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView" android:id="@+id/content_preview_image_1_large" diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml index 1d18648b9ef7..bc4f327d7a15 100644 --- a/core/res/res/layout/chooser_grid_preview_text.xml +++ b/core/res/res/layout/chooser_grid_preview_text.xml @@ -23,7 +23,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:background="?android:attr/colorBackgroundFloating"> + android:background="?android:attr/colorBackground"> <RelativeLayout android:layout_width="@dimen/chooser_preview_width" diff --git a/core/res/res/layout/chooser_list_per_profile.xml b/core/res/res/layout/chooser_list_per_profile.xml index 86dc71cbbfb8..912173cc725b 100644 --- a/core/res/res/layout/chooser_list_per_profile.xml +++ b/core/res/res/layout/chooser_list_per_profile.xml @@ -23,7 +23,7 @@ android:layoutManager="com.android.internal.app.ChooserGridLayoutManager" android:id="@+id/resolver_list" android:clipToPadding="false" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:scrollbars="none" android:elevation="1dp" android:nestedScrollingEnabled="true" /> diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml index dd79a0bb1817..5f1b60e8d0f2 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 4aa8acc363fa..000000000000 --- 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 bad9a6ba6184..e644cd55a86a 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" + <LinearLayout + android:id="@+id/notification_headerless_view_column" + android:layout_width="0px" + android:layout_height="wrap_content" + 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" > - <!-- - 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" + <NotificationTopLineView + android:id="@+id/notification_top_line" 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" - /> + android:layout_height="@dimen/notification_headerless_line_height" + android:clipChildren="false" + android:theme="@style/Theme.DeviceDefault.Notification" + > - <include layout="@layout/notification_top_line_views" /> + <!-- + 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" + /> - </NotificationTopLineView> + <include layout="@layout/notification_top_line_views" /> - <LinearLayout - android:id="@+id/notification_main_column" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/notification_heading_margin_end" - android:layout_marginStart="@dimen/notification_content_margin_start" - android:orientation="vertical" - > + </NotificationTopLineView> - <com.android.internal.widget.NotificationVanishingFrameLayout + <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" > - <!-- This is the simplest way to keep this text vertically centered without using - gravity="center_vertical" which causes jumpiness in expansion animations. --> + + <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_text" + layout="@layout/notification_template_progress" android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="center_vertical" - android:layout_marginTop="0dp" + android:layout_height="@dimen/notification_headerless_line_height" /> - </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 aa20ad36720b..ff64315572ca 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 542e59df76c0..2991b1706a64 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" > - <LinearLayout - android:id="@+id/notification_content_container" - 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" + + <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" > - <!-- 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" - /> + <!-- + 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. + --> - <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" + <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:gravity="top" android:singleLine="true" android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" /> - </LinearLayout> + + <include layout="@layout/notification_top_line_views" /> + + </NotificationTopLineView> + <LinearLayout - android:id="@+id/media_actions" - android:layout_width="wrap_content" + android:id="@+id/notification_main_column" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="top|end" - android:layout_marginStart="10dp" - android:layoutDirection="ltr" - android:orientation="horizontal" + 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_material_media_action" - android:id="@+id/action0" + 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" + /> + + <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/action1" /> - <include - layout="@layout/notification_material_media_action" - android:id="@+id/action2" + <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 + 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.MediaNotificationView> diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml index bdcfeb21598a..8594c33c3082 100644 --- a/core/res/res/layout/resolver_empty_states.xml +++ b/core/res/res/layout/resolver_empty_states.xml @@ -84,7 +84,7 @@ <TextView android:id="@+id/empty" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:text="@string/noApplications" android:padding="@dimen/chooser_edge_margin_normal" android:layout_marginBottom="56dp" diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml index 6fde1dfe248e..d791598a3552 100644 --- a/core/res/res/layout/resolver_list.xml +++ b/core/res/res/layout/resolver_list.xml @@ -68,7 +68,7 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <FrameLayout @@ -76,7 +76,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorBackgroundFloating"/> + android:background="?attr/colorBackground"/> <TabHost android:id="@+id/profile_tabhost" @@ -85,7 +85,7 @@ android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:accessibilityTraversalAfter="@id/title" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" @@ -101,7 +101,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical"/> <FrameLayout android:id="@android:id/tabcontent" @@ -120,13 +120,13 @@ android:layout_height="wrap_content" android:layout_alwaysShow="true" android:orientation="vertical" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:layout_ignoreOffset="true"> <View android:id="@+id/resolver_button_bar_divider" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <LinearLayout android:id="@+id/button_bar" diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml index 9410301e40fc..d6ca7abdca71 100644 --- a/core/res/res/layout/resolver_list_per_profile.xml +++ b/core/res/res/layout/resolver_list_per_profile.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:id="@+id/resolver_list" android:clipToPadding="false" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:elevation="@dimen/resolver_elevation" android:nestedScrollingEnabled="true" android:scrollbarStyle="outsideOverlay" diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml index 4a5aa020fb9f..7610e732ed06 100644 --- a/core/res/res/layout/resolver_list_with_default.xml +++ b/core/res/res/layout/resolver_list_with_default.xml @@ -148,7 +148,7 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <FrameLayout @@ -157,7 +157,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorBackgroundFloating"/> + android:background="?attr/colorBackground"/> <TabHost android:layout_alwaysShow="true" @@ -166,7 +166,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" @@ -182,7 +182,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical"/> <FrameLayout android:id="@android:id/tabcontent" @@ -200,6 +200,6 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> </com.android.internal.widget.ResolverDrawerLayout> diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml index 4410e944db39..baffa5a82cdb 100644 --- a/core/res/res/values-night/colors.xml +++ b/core/res/res/values-night/colors.xml @@ -36,7 +36,6 @@ <color name="resolver_empty_state_text">#FFFFFF</color> <color name="resolver_empty_state_icon">#FFFFFF</color> - <color name="chooser_chip_icon">#8AB4F8</color> <!-- Blue 300 --> <color name="personal_apps_suspension_notification_color">#8AB4F8</color> </resources> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 91896febc571..0213c60e9f60 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -237,7 +237,6 @@ <color name="resolver_text_color_secondary_dark">#ffC4C6C6</color> <color name="resolver_empty_state_text">#FF202124</color> <color name="resolver_empty_state_icon">#FF5F6368</color> - <color name="chooser_chip_icon">#FF1A73E8</color> <!-- Blue 600 --> <!-- Color for personal app suspension notification button text and icon tint. --> <color name="personal_apps_suspension_notification_color">#1A73E8</color> @@ -249,34 +248,34 @@ <color name="system_accent1_0">#ffffff</color> <!-- Shade of the accent system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_50">#91fff4</color> + <color name="system_accent1_50">#9CFFF2</color> <!-- Shade of the accent system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_100">#83f6e5</color> + <color name="system_accent1_100">#8DF5E3</color> <!-- Shade of the accent system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_200">#65d9c9</color> + <color name="system_accent1_200">#71D8C7</color> <!-- Shade of the accent system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_300">#45bdae</color> + <color name="system_accent1_300">#53BCAC</color> <!-- Shade of the accent system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_400">#1fa293</color> + <color name="system_accent1_400">#34A192</color> <!-- Shade of the accent system color at 49% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_500">#008377</color> + <color name="system_accent1_500">#008375</color> <!-- Shade of the accent system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_600">#006d61</color> + <color name="system_accent1_600">#006C5F</color> <!-- Shade of the accent system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_700">#005449</color> + <color name="system_accent1_700">#005747</color> <!-- Shade of the accent system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_800">#003c33</color> + <color name="system_accent1_800">#003E31</color> <!-- Shade of the accent system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_900">#00271e</color> + <color name="system_accent1_900">#002214</color> <!-- Darkest shade of the accent color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_accent1_1000">#000000</color> @@ -286,34 +285,34 @@ <color name="system_accent2_0">#ffffff</color> <!-- Shade of the secondary accent system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_50">#91fff4</color> + <color name="system_accent2_50">#CDFAF1</color> <!-- Shade of the secondary accent system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_100">#83f6e5</color> + <color name="system_accent2_100">#BFEBE3</color> <!-- Shade of the secondary accent system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_200">#65d9c9</color> + <color name="system_accent2_200">#A4CFC7</color> <!-- Shade of the secondary accent system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_300">#45bdae</color> + <color name="system_accent2_300">#89B4AC</color> <!-- Shade of the secondary accent system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_400">#1fa293</color> + <color name="system_accent2_400">#6F9991</color> <!-- Shade of the secondary accent system color at 49% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_500">#008377</color> + <color name="system_accent2_500">#537C75</color> <!-- Shade of the secondary accent system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_600">#006d61</color> + <color name="system_accent2_600">#3D665F</color> <!-- Shade of the secondary accent system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_700">#005449</color> + <color name="system_accent2_700">#254E47</color> <!-- Shade of the secondary accent system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_800">#003c33</color> + <color name="system_accent2_800">#0C3731</color> <!-- Shade of the secondary accent system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_900">#00271e</color> + <color name="system_accent2_900">#00211C</color> <!-- Darkest shade of the secondary accent color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_accent2_1000">#000000</color> @@ -323,34 +322,34 @@ <color name="system_accent3_0">#ffffff</color> <!-- Shade of the tertiary accent system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_50">#91fff4</color> + <color name="system_accent3_50">#F9EAFF</color> <!-- Shade of the tertiary accent system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_100">#83f6e5</color> + <color name="system_accent3_100">#ECDBFF</color> <!-- Shade of the tertiary accent system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_200">#65d9c9</color> + <color name="system_accent3_200">#CFBFEB</color> <!-- Shade of the tertiary accent system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_300">#45bdae</color> + <color name="system_accent3_300">#B3A4CF</color> <!-- Shade of the tertiary accent system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_400">#1fa293</color> + <color name="system_accent3_400">#988AB3</color> <!-- Shade of the tertiary accent system color at 49% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_500">#008377</color> + <color name="system_accent3_500">#7B6E96</color> <!-- Shade of the tertiary accent system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_600">#006d61</color> + <color name="system_accent3_600">#64587F</color> <!-- Shade of the tertiary accent system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_700">#005449</color> + <color name="system_accent3_700">#4C4165</color> <!-- Shade of the tertiary accent system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_800">#003c33</color> + <color name="system_accent3_800">#352B4D</color> <!-- Shade of the tertiary accent system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_900">#00271e</color> + <color name="system_accent3_900">#1E1636</color> <!-- Darkest shade of the tertiary accent color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_accent3_1000">#000000</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b4ef63fbcce3..a3b3bb5d3426 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 43dbd38d1dc4..afbbe467078c 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/strings.xml b/core/res/res/values/strings.xml index 7ea762c2fbbb..0b3c4052e204 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -822,6 +822,11 @@ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgroupdesc_camera">take pictures and record video</string> + <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]--> + <string name="permgrouplab_nearby_devices">Nearby Bluetooth Devices</string> + <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]--> + <string name="permgroupdesc_nearby_devices">discover and connect to nearby Bluetooth devices</string> + <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_calllog">Call logs</string> <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> @@ -1471,6 +1476,14 @@ <string name="permdesc_bluetooth" product="default">Allows the app to view the configuration of the Bluetooth on the phone, and to make and accept connections with paired devices.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]--> + <string name="permlab_bluetooth_scan">discover and pair nearby Bluetooth devices</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]--> + <string name="permdesc_bluetooth_scan" product="default">Allows the app to discover and pair nearby Bluetooth devices</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]--> + <string name="permlab_bluetooth_connect">connect to paired Bluetooth devices</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]--> + <string name="permdesc_bluetooth_connect" product="default">Allows the app to connect to paired Bluetooth devices</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index c7ded0cfa3a2..fbf67e0d84ac 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1481,17 +1481,6 @@ please see styles_device_defaults.xml. <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 1d74d85fb9db..d6a6f4dea220 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/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml index 6849a90f5010..f8c69ac17bb0 100644 --- a/core/tests/bluetoothtests/AndroidManifest.xml +++ b/core/tests/bluetoothtests/AndroidManifest.xml @@ -20,6 +20,8 @@ <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f31233b29cd0..408624a6f1b4 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -44,6 +44,8 @@ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 252938abffdb..0ea63643d24e 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 @@ public class NotificationTest { } @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/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java index 7cb680499d98..36da927116b7 100644 --- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java +++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java @@ -235,6 +235,7 @@ public class PeopleSpaceTileTest { .setStatuses(statusList).setNotificationKey("key") .setNotificationContent("content") .setNotificationDataUri(Uri.parse("data")) + .setMessagesCount(2) .setIntent(new Intent()) .build(); @@ -256,6 +257,7 @@ public class PeopleSpaceTileTest { assertThat(readTile.getNotificationKey()).isEqualTo(tile.getNotificationKey()); assertThat(readTile.getNotificationContent()).isEqualTo(tile.getNotificationContent()); assertThat(readTile.getNotificationDataUri()).isEqualTo(tile.getNotificationDataUri()); + assertThat(readTile.getMessagesCount()).isEqualTo(tile.getMessagesCount()); assertThat(readTile.getIntent().toString()).isEqualTo(tile.getIntent().toString()); } @@ -291,6 +293,17 @@ public class PeopleSpaceTileTest { } @Test + public void testMessagesCount() { + PeopleSpaceTile tile = + new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build(), + mLauncherApps) + .setMessagesCount(2) + .build(); + + assertThat(tile.getMessagesCount()).isEqualTo(2); + } + + @Test public void testIntent() { PeopleSpaceTile tile = new PeopleSpaceTile.Builder( new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build(); diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java index 11239db9f404..30b2d8e47ab1 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.Test; 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 d555cd90d907..009665fa1f53 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -18,13 +18,9 @@ package android.os; 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.ContentResolver; 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 @@ public class VibrationEffectTest { 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 @@ public class VibrationEffectTest { VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); private static final VibrationEffect TEST_WAVEFORM = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1); - private static final VibrationEffect TEST_COMPOSED = - VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0f, 100) - .compose(); @Test public void getRingtones_noPrebakedRingtones() { @@ -129,7 +118,16 @@ public class VibrationEffectTest { public void testValidateWaveform() { VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate(); VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate(); + VibrationEffect.startWaveform() + .addStep(/* amplitude= */ 1, /* duration= */ 10) + .addRamp(/* amplitude= */ 0, /* duration= */ 20) + .addStep(/* amplitude= */ 1, /* frequency*/ 1, /* duration= */ 100) + .addRamp(/* amplitude= */ 0.5f, /* frequency*/ -1, /* duration= */ 50) + .build() + .validate(); + assertThrows(IllegalStateException.class, + () -> VibrationEffect.startWaveform().build().validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createWaveform(new long[0], new int[0], -1).validate()); assertThrows(IllegalArgumentException.class, @@ -143,17 +141,31 @@ public class VibrationEffectTest { assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createWaveform( TEST_TIMINGS, TEST_AMPLITUDES, TEST_TIMINGS.length).validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startWaveform() + .addStep(/* amplitude= */ -2, 10).build().validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startWaveform() + .addStep(1, /* duration= */ -1).build().validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startWaveform() + .addStep(1, 0, /* duration= */ -1).build().validate()); } @Test public void testValidateComposed() { VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addEffect(TEST_ONE_SHOT) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) + .addEffect(TEST_WAVEFORM, 100) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) + .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .compose() .validate(); + assertThrows(IllegalStateException.class, + () -> VibrationEffect.startComposition().compose().validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startComposition().addPrimitive(-1).compose().validate()); assertThrows(IllegalArgumentException.class, @@ -163,256 +175,138 @@ public class VibrationEffectTest { .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, -10) + .compose() + .validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startComposition() + .addEffect(TEST_ONE_SHOT, /* delay= */ -10) + .compose() + .validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1) .compose() .validate()); } @Test - public void testScalePrebaked_scalesFallbackEffect() { - VibrationEffect.Prebaked prebaked = - (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]); - assertSame(prebaked, prebaked.scale(0.5f)); - - prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, TEST_ONE_SHOT); - VibrationEffect.OneShot scaledFallback = - (VibrationEffect.OneShot) prebaked.scale(0.5f).getFallbackEffect(); - assertEquals(34, scaledFallback.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - } + public void testResolveOneShot() { + VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51); + assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude()); - @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()); + assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000)); } @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); + public void testResolveWaveform() { + VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102); + assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude()); - // 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()); + assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000)); } @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()); - - // Ignores input when amplitude already set. - VibrationEffect.OneShot resolved2 = resolved.resolve(10); - assertSame(resolved, resolved2); - assertEquals(239, resolved2.getAmplitude()); + public void testResolvePrebaked() { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + assertEquals(effect, effect.resolve(51)); } @Test - public void testResolveOneshotFailsWhenMaxAmplitudeAboveThreshold() { - try { - TEST_ONE_SHOT.resolve(1000); - fail("Max amplitude above threshold, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + public void testResolveComposed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1) + .compose(); + assertEquals(effect, effect.resolve(51)); + + VibrationEffect.Composed resolved = VibrationEffect.startComposition() + .addEffect(DEFAULT_ONE_SHOT) + .compose() + .resolve(51); + assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude()); } @Test - public void testResolveOneshotFailsWhenAmplitudeNonPositive() { - try { - TEST_ONE_SHOT.resolve(0); - fail("Amplitude is set to zero, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + public void testApplyEffectStrengthOneShot() { + VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength( + VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(DEFAULT_ONE_SHOT, applied); } @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()); + public void testApplyEffectStrengthWaveform() { + VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength( + VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(TEST_WAVEFORM, applied); } @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()); - - // Ignores input when amplitude already set. - VibrationEffect.Waveform resolved2 = resolved.resolve(10); - assertSame(resolved, resolved2); - assertArrayEquals(new int[]{255, 0, 123}, resolved2.getAmplitudes()); + 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 testResolveWaveformFailsWhenMaxAmplitudeAboveThreshold() { - try { - TEST_WAVEFORM.resolve(1000); - fail("Max amplitude above threshold, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + public void testApplyEffectStrengthComposed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1) + .compose(); + assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT)); + + VibrationEffect.Composed applied = VibrationEffect.startComposition() + .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .compose() + .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, + ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength()); } @Test - public void 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); - } + public void testScaleOneShot() { + VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f); + assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude()); - @Test - public void testResolveComposed_ignoresDefaultAmplitudeAndReturnsSameEffect() { - VibrationEffect initial = TEST_COMPOSED; - assertSame(initial, initial.resolve(1000)); + VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f); + assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude()); } @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) VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, - TEST_AMPLITUDE / 255f) - .compose(); + public void testScaleWaveform() { + VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f); + assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f); - 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 scaledDown = TEST_WAVEFORM.scale(0.5f); + assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude()); } @Test - public void testScaleOnMaxAmplitude() { - VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot( - TEST_TIMING, VibrationEffect.MAX_AMPLITUDE); - VibrationEffect.Waveform waveform = new VibrationEffect.Waveform( - new long[]{TEST_TIMING}, new int[]{VibrationEffect.MAX_AMPLITUDE}, -1); - VibrationEffect.Composed composed = - (VibrationEffect.Composed) VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) - .compose(); + public void testScalePrebaked() { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - // 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); - } + VibrationEffect.Composed scaledUp = effect.scale(1.5f); + assertEquals(effect, scaledUp); - @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()); + VibrationEffect.Composed scaledDown = effect.scale(0.5f); + assertEquals(effect, scaledDown); } @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()); - } + public void testScaleComposed() { + VibrationEffect.Composed effect = + (VibrationEffect.Composed) VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1) + .addEffect(TEST_ONE_SHOT) + .compose(); - @Test - public void getFallbackEffect_withoutEffectSet_isNull() { - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - true, VibrationEffect.EFFECT_STRENGTH_LIGHT); - Assert.assertNull(effect.getFallbackEffect()); - } + VibrationEffect.Composed scaledUp = effect.scale(1.5f); + assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale()); + assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude()); - @Test - public void getFallbackEffect_withFallback_returnsValueFromConstructor() { - VibrationEffect fallback = VibrationEffect.createOneShot(100, 100); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_LIGHT, fallback); - Assert.assertEquals(fallback, effect.getFallbackEffect()); + VibrationEffect.Composed scaledDown = effect.scale(0.5f); + assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale()); + assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude()); } private Resources mockRingtoneResources() { @@ -448,4 +342,4 @@ public class VibrationEffectTest { return context; } -}
\ No newline at end of file +} 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 000000000000..de80812f9c29 --- /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 000000000000..538655bb394b --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class PrimitiveSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10); + + assertEquals(-1, primitive.getDuration()); + assertTrue(primitive.hasNonZeroAmplitude()); + assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId()); + assertEquals(10, primitive.getDelay()); + assertEquals(1f, primitive.getScale(), TOLERANCE); + } + + @Test + public void testSerialization() { + PrimitiveSegment original = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, PrimitiveSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(1000, 0, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, -1, 0) + .validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, 1, -1) + .validate()); + } + + @Test + public void testResolve_ignoresAndReturnsSameEffect() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + assertSame(primitive, primitive.resolve(1000)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + assertSame(primitive, + primitive.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale_fullPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + + assertEquals(1f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0.34f, initial.scale(0.5f).getScale(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.71f, initial.scale(0.8f).getScale(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } + + @Test + public void testScale_halfPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0); + + assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getScale(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(0.86f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.35f, initial.scale(0.8f).getScale(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } + + @Test + public void testScale_zeroPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0); + + assertEquals(0f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } +} diff --git a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java new file mode 100644 index 000000000000..174b4a76b3c3 --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class RampSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0, + /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100); + + assertEquals(100L, ramp.getDuration()); + assertTrue(ramp.hasNonZeroAmplitude()); + assertEquals(1f, ramp.getStartAmplitude()); + assertEquals(0f, ramp.getEndAmplitude()); + assertEquals(-1f, ramp.getStartFrequency()); + assertEquals(1f, ramp.getEndFrequency()); + } + + @Test + public void testSerialization() { + RampSegment original = new RampSegment(0, 1, 0, 0.5f, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, RampSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0, + /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0, 0, 0).validate()); + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(/* startAmplitude= */ -2, 0, 0, 0, 0).validate()); + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(0, /* endAmplitude= */ 2, 0, 0, 0).validate()); + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(0, 0, 0, 0, /* duration= */ -1).validate()); + } + + @Test + public void testHasNonZeroAmplitude() { + assertTrue(new RampSegment(0, 1, 0, 0, 0).hasNonZeroAmplitude()); + assertTrue(new RampSegment(0.01f, 0, 0, 0, 0).hasNonZeroAmplitude()); + assertFalse(new RampSegment(0, 0, 0, 0, 0).hasNonZeroAmplitude()); + } + + @Test + public void testResolve() { + RampSegment ramp = new RampSegment(0, 1, 0, 0, 0); + assertSame(ramp, ramp.resolve(100)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + RampSegment ramp = new RampSegment(1, 0, 1, 0, 0); + assertSame(ramp, ramp.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale() { + RampSegment initial = new RampSegment(0, 1, 0, 0, 0); + + assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE); + + assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE); + assertEquals(0.34f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(1f, initial.scale(1.5f).getEndAmplitude(), TOLERANCE); + assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getEndAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.71f, initial.scale(0.8f).getEndAmplitude(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getEndAmplitude(), TOLERANCE); + } + + @Test + public void testScale_halfPrimitiveScaleValue() { + RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0); + + assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE); + } +} diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java new file mode 100644 index 000000000000..79529b8cb13c --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class StepSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequency= */ -1f, + /* duration= */ 100); + + assertEquals(100, step.getDuration()); + assertTrue(step.hasNonZeroAmplitude()); + assertEquals(1f, step.getAmplitude()); + assertEquals(-1f, step.getFrequency()); + } + + @Test + public void testSerialization() { + StepSegment original = new StepSegment(0.5f, 1f, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, StepSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new StepSegment(/* amplitude= */ 0f, /* frequency= */ -1f, /* duration= */ 100).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(/* amplitude= */ -2, 1f, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(/* amplitude= */ 2, 1f, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(2, 1f, /* duration= */ -1).validate()); + } + + @Test + public void testHasNonZeroAmplitude() { + assertTrue(new StepSegment(1f, 0, 0).hasNonZeroAmplitude()); + assertTrue(new StepSegment(0.01f, 0, 0).hasNonZeroAmplitude()); + assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0).hasNonZeroAmplitude()); + assertFalse(new StepSegment(0, 0, 0).hasNonZeroAmplitude()); + } + + @Test + public void testResolve() { + StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); + assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude()); + assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE); + + StepSegment resolved = new StepSegment(0, 0, 0); + assertSame(resolved, resolved.resolve(100)); + + assertThrows(IllegalArgumentException.class, () -> resolved.resolve(1000)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); + assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale_fullAmplitude() { + StepSegment initial = new StepSegment(1f, 0, 0); + + assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0.34f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.71f, initial.scale(0.8f).getAmplitude(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_halfAmplitude() { + StepSegment initial = new StepSegment(0.5f, 0, 0); + + assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(0.86f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.35f, initial.scale(0.8f).getAmplitude(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_zeroAmplitude() { + StepSegment initial = new StepSegment(0, 0, 0); + + assertEquals(0f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_defaultAmplitude() { + StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); + + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(0.5f).getAmplitude(), + TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(), + TOLERANCE); + } +} diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java new file mode 100644 index 000000000000..2dd3f69852c1 --- /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/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml index 0898faeb7080..b3b34ef93ebe 100644 --- a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml @@ -32,6 +32,8 @@ <uses-permission android:name="android.permission.BIND_INPUT_METHOD" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BRICK" /> <uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED" /> <uses-permission android:name="android.permission.BROADCAST_SMS" /> diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml index 98f7177992e2..42d94071400d 100644 --- a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml @@ -21,6 +21,9 @@ android:sharedUserId="com.android.framework.externalsharedpermstestapp"> <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/data/etc/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml index ac16af348471..abde232a8a5a 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/data/etc/platform.xml b/data/etc/platform.xml index 27bf4ef4c84d..8da4a37e44cd 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -224,7 +224,22 @@ targetSdk="29"> <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" /> </split-permission> - + <split-permission name="android.permission.BLUETOOTH" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_SCAN" /> + </split-permission> + <split-permission name="android.permission.BLUETOOTH" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_CONNECT" /> + </split-permission> + <split-permission name="android.permission.BLUETOOTH_ADMIN" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_SCAN" /> + </split-permission> + <split-permission name="android.permission.BLUETOOTH_ADMIN" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_CONNECT" /> + </split-permission> <!-- This is a list of all the libraries available for application code to link against. --> diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 3aaf11cef17c..4534d36342db 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -28,7 +28,7 @@ public final class BLASTBufferQueue { 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 @@ public final class BLASTBufferQueue { /** 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/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java index cd77d9c2723f..ec3b102c5af9 100644 --- a/keystore/java/android/security/keystore/AttestationUtils.java +++ b/keystore/java/android/security/keystore/AttestationUtils.java @@ -254,6 +254,8 @@ public abstract class AttestationUtils { keyStore.deleteEntry(keystoreAlias); return certificateChain; + } catch (SecurityException e) { + throw e; } catch (Exception e) { throw new DeviceIdAttestationException("Unable to perform attestation", e); } diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index d2b3cf6a4fe2..6bd0e0a4ff86 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 4768cf58152b..fa94ec557883 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 final class OneHandedSettingsUtil { */ 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 3f46fee26510..28c8f53c7f26 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.ColorDrawable; 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView); } splashScreenView.makeSystemUIColorsTransparent(); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return splashScreenView; } } @@ -395,7 +391,7 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { 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 @@ public class SplashscreenContentDrawer { } 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 8626dbc6d724..a4a83eb87b94 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.Paint; 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 @@ import java.util.function.Consumer; 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 b592121e98c0..b5008137cedd 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 @@ public class StartingSurfaceDrawer { } 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 @@ public class StartingSurfaceDrawer { 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 @@ public class StartingSurfaceDrawer { 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 @@ public class StartingSurfaceDrawer { 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 @@ public class StartingSurfaceDrawer { removeWindowNoAnimate(taskId); saveSplashScreenRecord(taskId, view); } - return shouldSaveView; } private void saveSplashScreenRecord(int taskId, View view) { @@ -358,13 +361,6 @@ public class StartingSurfaceDrawer { 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 207db9e80511..8ae0a7356574 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 @@ public class StartingSurfaceDrawerTests { } @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/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl index 50ed046f09cd..092ec67ff146 100644 --- a/location/java/android/location/provider/ILocationProviderManager.aidl +++ b/location/java/android/location/provider/ILocationProviderManager.aidl @@ -24,7 +24,7 @@ import android.location.provider.ProviderProperties; * @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 ae6395d5d12d..eada22cd94dc 100644 --- a/location/java/android/location/provider/LocationProviderBase.java +++ b/location/java/android/location/provider/LocationProviderBase.java @@ -96,7 +96,6 @@ public abstract class LocationProviderBase { "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 abstract class LocationProviderBase { 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 abstract class LocationProviderBase { 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 85a083ee84bc..ade0ea40e157 100644 --- a/location/java/android/location/util/identity/CallerIdentity.java +++ b/location/java/android/location/util/identity/CallerIdentity.java @@ -55,6 +55,20 @@ public final class CallerIdentity { } /** + * 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 @@ public final class CallerIdentity { } } - /** - * 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 7f1cf6dc3459..95f6c2f38ecb 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 abstract class LocationProviderBase { 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 abstract class LocationProviderBase { 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 abstract class LocationProviderBase { 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 3cd615b658b2..9774e8032c95 100644 --- a/media/java/android/media/AudioProfile.java +++ b/media/java/android/media/AudioProfile.java @@ -18,6 +18,9 @@ package android.media; 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 class AudioProfile { 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 cf31e4141a6d..a7e2b65ebb8f 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -1674,9 +1674,11 @@ final public class MediaCodec implements PlaybackComponent { 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 @@ final public class MediaCodec implements PlaybackComponent { 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 @@ final public class MediaCodec implements PlaybackComponent { 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 @@ final public class MediaCodec implements PlaybackComponent { mEventHandler = null; } mCallbackHandler = mEventHandler; + mOnFirstTunnelFrameReadyHandler = mEventHandler; mOnFrameRenderedHandler = mEventHandler; mBufferLock = new Object(); @@ -2277,6 +2291,9 @@ final public class MediaCodec implements PlaybackComponent { 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 @@ final public class MediaCodec implements PlaybackComponent { 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 @@ final public class MediaCodec implements PlaybackComponent { } /** + * 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 @@ final public class MediaCodec implements PlaybackComponent { 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 3de78bb9ef9f..644afb79814f 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -99,9 +99,7 @@ import java.util.concurrent.Executor; /** - * 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 02fa0401e586..8daa30389012 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -299,6 +299,24 @@ public final class MediaRouter2 { } /** + * 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 7f5dd5d15dbe..ded2e1b4e939 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -81,6 +81,7 @@ enum { EVENT_CALLBACK = 1, EVENT_SET_CALLBACK = 2, EVENT_FRAME_RENDERED = 3, + EVENT_FIRST_TUNNEL_FRAME_READY = 4, }; static struct CryptoErrorCodes { @@ -269,6 +270,18 @@ JMediaCodec::~JMediaCodec() { 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 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { 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 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { } break; } + case kWhatFirstTunnelFrameReady: + { + handleFirstTunnelFrameReadyNotification(msg); + break; + } default: TRESPASS(); } @@ -1256,6 +1295,22 @@ static jint throwExceptionAsNecessary( } } +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 @@ static const JNINativeMethod gMethods[] = { { "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 f16bcf3c88e4..5fd6bfd6f8cd 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -63,6 +63,8 @@ struct JMediaCodec : public AHandler { void release(); void releaseAsync(); + status_t enableOnFirstTunnelFrameReadyListener(jboolean enable); + status_t enableOnFrameRenderedListener(jboolean enable); status_t setCallback(jobject cb); @@ -176,6 +178,7 @@ private: kWhatCallbackNotify, kWhatFrameRendered, kWhatAsyncReleaseComplete, + kWhatFirstTunnelFrameReady, }; jclass mClass; @@ -191,6 +194,7 @@ private: std::once_flag mAsyncReleaseFlag; sp<AMessage> mCallbackNotification; + sp<AMessage> mOnFirstTunnelFrameReadyNotification; sp<AMessage> mOnFrameRenderedNotification; status_t mInitStatus; @@ -203,6 +207,7 @@ private: jobject *buf) const; void handleCallback(const sp<AMessage> &msg); + void handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg); void handleFrameRenderedNotification(const sp<AMessage> &msg); DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec); diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml index fc96fd926e2d..3794ccddb48f 100644 --- a/media/packages/BluetoothMidiService/AndroidManifest.xml +++ b/media/packages/BluetoothMidiService/AndroidManifest.xml @@ -27,6 +27,9 @@ <uses-feature android:name="android.software.midi" android:required="true"/> <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <application tools:replace="android:label" diff --git a/media/tests/ScoAudioTest/AndroidManifest.xml b/media/tests/ScoAudioTest/AndroidManifest.xml index a0fba733370e..5af77ee9d35d 100644 --- a/media/tests/ScoAudioTest/AndroidManifest.xml +++ b/media/tests/ScoAudioTest/AndroidManifest.xml @@ -22,6 +22,9 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.BROADCAST_STICKY"/> <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <application> <activity android:label="@string/app_name" diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index b01878b3070b..85513cad489c 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -285,6 +285,7 @@ LIBANDROID { 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 @@ LIBANDROID_PLATFORM { 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 8d4e9009cc56..cc8dd727408f 100644 --- a/native/android/libandroid_net.map.txt +++ b/native/android/libandroid_net.map.txt @@ -14,6 +14,8 @@ LIBANDROID_NET { 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 a8104fc23041..d4b888845b27 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 @@ static int getnetidfromhandle(net_handle_t handle, unsigned *netid) { 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 @@ int android_setprocnetwork(net_handle_t network) { 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/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index f9795c601431..d36836c1af19 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -25,6 +25,8 @@ <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index 657d5a3d2eae..3553c1f05310 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -128,6 +128,7 @@ cc_library_static { srcs: [ "jni/android_net_NetworkUtils.cpp", ], + shared_libs: ["libandroid_net"], apex_available: [ "//apex_available:platform", "com.android.tethering", @@ -140,6 +141,7 @@ cc_library_shared { 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 ad44b27f6d0b..0a9560a5c56d 100644 --- a/packages/Connectivity/framework/api/current.txt +++ b/packages/Connectivity/framework/api/current.txt @@ -68,6 +68,7 @@ package android.net { 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 @@ package android.net { 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 663a4ff9dbf5..f7c3965a7d24 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -11,6 +11,7 @@ package android.net { method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange(); method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress); @@ -47,6 +48,7 @@ package android.net { 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 @@ -59,7 +61,14 @@ package android.net { 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 703fca408f7a..b19efa3640c3 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -212,10 +212,12 @@ package android.net { 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 @@ package android.net { 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 @@ package android.net { 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 @@ package android.net { 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 @@ package android.net { } 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 c5b1ff811260..c7c0beee5ba2 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 void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobjec } } -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 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, j // 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 f2078307a59a..20ff93f5d90a 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -1083,8 +1083,7 @@ public class ConnectivityManager { * * @return a {@link Network} object for the current default network for the * given UID or {@code null} if no default network is currently active - * - * @hide + * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) @Nullable @@ -3705,8 +3704,9 @@ public class ConnectivityManager { private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>(); private static CallbackHandler sCallbackHandler; - private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, - int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + private NetworkRequest sendRequestForNetwork(int asUid, NetworkCapabilities need, + NetworkCallback callback, int timeoutMs, NetworkRequest.Type reqType, int legacyType, + CallbackHandler handler) { printStackTrace(); checkCallbackNotNull(callback); if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) { @@ -3731,8 +3731,8 @@ public class ConnectivityManager { getAttributionTag()); } else { request = mService.requestNetwork( - need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, - callbackFlags, callingPackageName, getAttributionTag()); + asUid, need, reqType.ordinal(), messenger, timeoutMs, binder, + legacyType, callbackFlags, callingPackageName, getAttributionTag()); } if (request != null) { sCallbacks.put(request, callback); @@ -3747,6 +3747,12 @@ public class ConnectivityManager { return request; } + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, + int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType, + legacyType, handler); + } + /** * Helper function to request a network with a particular legacy type. * @@ -4232,8 +4238,40 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + registerDefaultNetworkCallbackAsUid(Process.INVALID_UID, networkCallback, handler); + } + + /** + * Registers to receive notifications about changes in the default network for the specified + * UID. This may be a physical network or a virtual network, such as a VPN that applies to the + * UID. The callbacks will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param uid the UID for which to track default network changes. + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * UID's default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void registerDefaultNetworkCallbackAsUid(int uid, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { CallbackHandler cbHandler = new CallbackHandler(handler); - sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + sendRequestForNetwork(uid, null /* need */, networkCallback, 0 /* timeoutMs */, TRACK_DEFAULT, TYPE_NONE, cbHandler); } diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 3300fa8fd12a..0826922e2165 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -142,7 +142,7 @@ interface IConnectivityManager in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config, in int factorySerialNumber); - NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, + NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType, in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, int callbackFlags, String callingPackageName, String callingAttributionTag); diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java index 0741414ab3aa..41fad6317a20 100644 --- a/packages/Connectivity/framework/src/android/net/Network.java +++ b/packages/Connectivity/framework/src/android/net/Network.java @@ -27,7 +27,6 @@ import android.os.Parcelable; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; @@ -526,11 +525,4 @@ public class Network implements Parcelable { public String toString() { return Integer.toString(netId); } - - /** @hide */ - public void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - proto.write(NetworkProto.NET_ID, netId); - proto.end(token); - } } diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java index 3863ed1113f3..b3d9616cead7 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java @@ -362,9 +362,8 @@ public abstract class NetworkAgent { 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 @@ public abstract class NetworkAgent { * @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 @@ public abstract class NetworkAgent { * @hide */ @Deprecated + @SystemApi public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) { mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName); queueOrSendNetworkInfo(mNetworkInfo); @@ -962,6 +961,7 @@ public abstract class NetworkAgent { * 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 0bd2371bfca8..3f058d8cbf12 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java @@ -175,6 +175,12 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * 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. * @@ -200,6 +206,13 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * 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> @@ -235,6 +248,8 @@ public final class NetworkAgentConfig implements Parcelable { skip464xlat = nac.skip464xlat; legacyType = nac.legacyType; legacyTypeName = nac.legacyTypeName; + legacySubType = nac.legacySubType; + legacySubTypeName = nac.legacySubTypeName; mLegacyExtraInfo = nac.mLegacyExtraInfo; } } @@ -300,7 +315,6 @@ public final class NetworkAgentConfig implements Parcelable { * 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() { @@ -313,7 +327,6 @@ public final class NetworkAgentConfig implements Parcelable { * perform its own carrier-specific provisioning procedure. * * @return this builder, to facilitate chaining. - * @hide */ @NonNull public Builder disableProvisioningNotification() { @@ -334,6 +347,18 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * 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. @@ -345,10 +370,20 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * 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) { @@ -435,6 +470,8 @@ public final class NetworkAgentConfig implements Parcelable { out.writeInt(skip464xlat ? 1 : 0); out.writeInt(legacyType); out.writeString(legacyTypeName); + out.writeInt(legacySubType); + out.writeString(legacySubTypeName); out.writeString(mLegacyExtraInfo); } @@ -452,6 +489,8 @@ public final class NetworkAgentConfig implements Parcelable { 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 c9c0940dfdf5..27f7ee28130d 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -35,7 +35,6 @@ import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; import android.util.Range; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.CollectionUtils; @@ -538,43 +537,6 @@ public final class NetworkCapabilities implements Parcelable { | (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 +601,31 @@ public final class NetworkCapabilities implements Parcelable { } /** - * 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 +697,7 @@ public final class NetworkCapabilities implements Parcelable { } /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public boolean hasUnwantedCapability(@NetCapability int capability) { return isValidCapability(capability) && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0); @@ -736,10 +711,16 @@ public final class NetworkCapabilities implements Parcelable { 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 +773,12 @@ public final class NetworkCapabilities implements Parcelable { } /** - * 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); } } @@ -2068,34 +2024,6 @@ public final class NetworkCapabilities implements Parcelable { } } - /** @hide */ - public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - - for (int transport : getTransportTypes()) { - proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport); - } - - for (int capability : getCapabilities()) { - proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability); - } - - proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps); - proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps); - - if (mNetworkSpecifier != null) { - proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString()); - } - if (mTransportInfo != null) { - // TODO b/120653863: write transport-specific info to proto? - } - - proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength()); - proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength); - - proto.end(token); - } - /** * @hide */ diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index f9b3db12c087..5313f08fffbe 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -47,7 +47,6 @@ import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; import android.util.Range; -import android.util.proto.ProtoOutputStream; import java.util.Arrays; import java.util.List; @@ -313,12 +312,31 @@ public class NetworkRequest implements Parcelable { * * @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 +593,7 @@ public class NetworkRequest implements Parcelable { * * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public boolean hasUnwantedCapability(@NetCapability int capability) { return networkCapabilities.hasUnwantedCapability(capability); } @@ -655,18 +674,6 @@ public class NetworkRequest implements Parcelable { } } - /** @hide */ - public void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - - proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type)); - proto.write(NetworkRequestProto.REQUEST_ID, requestId); - proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType); - networkCapabilities.dumpDebug(proto, NetworkRequestProto.NETWORK_CAPABILITIES); - - proto.end(token); - } - public boolean equals(@Nullable Object obj) { if (obj instanceof NetworkRequest == false) return false; NetworkRequest that = (NetworkRequest)obj; @@ -679,4 +686,43 @@ public class NetworkRequest implements Parcelable { 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 eadcb2d0a7f4..65849930fa4a 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 @@ import com.android.internal.annotations.VisibleForTesting; * 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 @@ public final class NetworkScore implements Parcelable { /** * @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 c4bebc0a982e..16ae55f8c11e 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 class NetworkUtils { */ 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 @@ public class NetworkUtils { * 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 d007a9520cb5..f6cae7251609 100644 --- a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java +++ b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java @@ -55,36 +55,68 @@ public abstract class SocketKeepalive implements AutoCloseable { 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 abstract class SocketKeepalive implements AutoCloseable { */ 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 @@ public abstract class SocketKeepalive implements AutoCloseable { 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 @@ public abstract class SocketKeepalive implements AutoCloseable { 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 1330e719e774..37dd9ff84b59 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -51,22 +51,33 @@ cc_library_shared { 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 { java_library { name: "service-connectivity-protos", + sdk_version: "system_current", proto: { type: "nano", }, @@ -93,6 +105,7 @@ java_library { 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 7cc599499ca1..c4c60ea5aaa8 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 @@ public class FusedLocationServiceTest { } @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 @@ public class FusedLocationServiceTest { } @Override - public void onFlushComplete() { - - } + public void onFlushComplete() {} public Location getNextLocation(long timeoutMs) throws InterruptedException { return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index d801f1bbf5e5..7186ec5be805 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1467,7 +1467,7 @@ <string name="data_connection_5g_plus" translatable="false">5G+</string> <!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] --> - <string name="data_connection_carrier_wifi">CWF</string> + <string name="data_connection_carrier_wifi">W+</string> <!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] --> <string name="cell_data_off_content_description">Mobile data off</string> diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 5d4078d3419b..fbb84fdfcedc 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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 2b4fef0c9ba7..536c65b41db1 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -45,6 +45,9 @@ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> @@ -237,6 +240,9 @@ <!-- Permission needed to run keyguard manager tests in CTS --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" /> + <!-- Permission needed to set/clear/verify lockscreen credentials in CTS tests --> + <uses-permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" /> + <!-- Permission needed to test wallpaper component --> <uses-permission android:name="android.permission.SET_WALLPAPER" /> <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 3904201d2ee8..4135bbe3e86d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -62,6 +62,8 @@ <!-- Networking and telephony --> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> diff --git a/packages/SystemUI/res/drawable/people_space_messages_count_background.xml b/packages/SystemUI/res/drawable/people_space_messages_count_background.xml new file mode 100644 index 000000000000..0fc112e16ee4 --- /dev/null +++ b/packages/SystemUI/res/drawable/people_space_messages_count_background.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + <solid android:color="#9ED582" /> + <corners android:radius="@dimen/people_space_messages_count_radius" /> +</shape> diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml index e4e4cd8956a7..db1d46d2117a 100644 --- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml +++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml @@ -98,8 +98,8 @@ android:orientation="horizontal" android:paddingTop="4dp" android:layout_width="match_parent" - android:layout_height="wrap_content"> - + android:layout_height="wrap_content" + android:clipToOutline="true"> <TextView android:id="@+id/name" android:gravity="center_vertical" @@ -112,7 +112,21 @@ android:ellipsize="end" android:layout_width="wrap_content" android:layout_height="wrap_content" /> - + <TextView + android:id="@+id/messages_count" + android:gravity="end" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textColor="?android:attr/textColorPrimary" + android:background="@drawable/people_space_messages_count_background" + android:textSize="14sp" + android:maxLines="1" + android:ellipsize="end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + /> <ImageView android:id="@+id/predefined_icon" android:gravity="end|center_vertical" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ff4e8e002d88..2393b749b64e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1353,6 +1353,7 @@ <dimen name="people_space_widget_radius">28dp</dimen> <dimen name="people_space_image_radius">20dp</dimen> + <dimen name="people_space_messages_count_radius">12dp</dimen> <dimen name="people_space_widget_background_padding">6dp</dimen> <dimen name="required_width_for_medium">146dp</dimen> <dimen name="required_width_for_large">138dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 691d111089b8..94bf86ab07da 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2865,6 +2865,8 @@ <string name="empty_status">Let’s chat tonight!</string> <!-- Default text for missed call notifications on their Conversation widget [CHAR LIMIT=20] --> <string name="missed_call">Missed call</string> + <!-- Text when a Notification may have more messages than the number indicated [CHAR LIMIT=5] --> + <string name="messages_count_overflow_indicator"><xliff:g id="number" example="7">%d</xliff:g>+</string> <!-- Description text for adding a Conversation widget [CHAR LIMIT=100] --> <string name="people_tile_description">See recent messages, missed calls, and status updates</string> <!-- Title text displayed for the Conversation widget [CHAR LIMIT=50] --> diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 93ce5a83f684..5bc128062adc 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -274,13 +274,17 @@ public class PeopleSpaceUtils { return tile; } boolean isMissedCall = Objects.equals(notification.category, CATEGORY_MISSED_CALL); - Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(notification); + List<Notification.MessagingStyle.Message> messages = + getMessagingStyleMessages(notification); - if (!isMissedCall && message == null) { + if (!isMissedCall && ArrayUtils.isEmpty(messages)) { if (DEBUG) Log.d(TAG, "Notification has no content"); return tile; } + // messages are in chronological order from most recent to least. + Notification.MessagingStyle.Message message = messages != null ? messages.get(0) : null; + int messagesCount = messages != null ? messages.size() : 0; // If it's a missed call notification and it doesn't include content, use fallback value, // otherwise, use notification content. boolean hasMessageText = message != null && !TextUtils.isEmpty(message.getText()); @@ -294,12 +298,16 @@ public class PeopleSpaceUtils { .setNotificationCategory(notification.category) .setNotificationContent(content) .setNotificationDataUri(dataUri) + .setMessagesCount(messagesCount) .build(); } - /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */ + /** + * Returns {@link Notification.MessagingStyle.Message}s from the Notification in chronological + * order from most recent to least. + */ @VisibleForTesting - public static Notification.MessagingStyle.Message getLastMessagingStyleMessage( + public static List<Notification.MessagingStyle.Message> getMessagingStyleMessages( Notification notification) { if (notification == null) { return null; @@ -312,7 +320,7 @@ public class PeopleSpaceUtils { Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages); sortedMessages.sort(Collections.reverseOrder( Comparator.comparing(Notification.MessagingStyle.Message::getTimestamp))); - return sortedMessages.get(0); + return sortedMessages; } } return null; diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index ae81ab04ec10..bc196bf68b66 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -58,8 +58,10 @@ import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; +import java.text.NumberFormat; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; @@ -82,6 +84,8 @@ class PeopleTileViewHelper { private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8; private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4; + private static final int MESSAGES_COUNT_OVERFLOW = 7; + private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+"); @@ -97,6 +101,9 @@ class PeopleTileViewHelper { private int mHeight; private int mLayoutSize; + private Locale mLocale; + private NumberFormat mIntegerFormat; + PeopleTileViewHelper(Context context, PeopleSpaceTile tile, int appWidgetId, Bundle options) { mContext = context; @@ -354,12 +361,35 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.image, View.GONE); views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message); } + if (mTile.getMessagesCount() > 1 && mLayoutSize == LAYOUT_MEDIUM) { + views.setViewVisibility(R.id.messages_count, View.VISIBLE); + views.setTextViewText(R.id.messages_count, + getMessagesCountText(mTile.getMessagesCount())); + } // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and // subtract 1 from maxLines when present. views.setViewVisibility(R.id.subtext, View.GONE); return views; } + // Some messaging apps only include up to 7 messages in their notifications. + private String getMessagesCountText(int count) { + if (count >= MESSAGES_COUNT_OVERFLOW) { + return mContext.getResources().getString( + R.string.messages_count_overflow_indicator, MESSAGES_COUNT_OVERFLOW); + } + + // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed + // non-null, so the first time this is called we will always get the appropriate + // NumberFormat, then never regenerate it unless the locale changes on the fly. + final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0); + if (!curLocale.equals(mLocale)) { + mLocale = curLocale; + mIntegerFormat = NumberFormat.getIntegerInstance(curLocale); + } + return mIntegerFormat.format(count); + } + private RemoteViews createStatusRemoteViews(ConversationStatus status) { RemoteViews views = getViewForContentLayout(); CharSequence statusText = status.getDescription(); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index 4ad685eae107..776e8a246bf6 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -328,6 +328,7 @@ public class PeopleSpaceWidgetManager { .setNotificationKey(null) .setNotificationContent(null) .setNotificationDataUri(null) + .setMessagesCount(0) // Reset missed calls category. .setNotificationCategory(null) .build(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index eedcdab68b9f..b1689f665ebb 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.privacy.logging.PrivacyLogger; 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mUiEventLogger = uiEventLogger; mQSTileHost = qsTileHost; mStatusBarIconController = statusBarIconController; - mCommandQueue = commandQueue; mDemoModeController = demoModeController; mUserTracker = userTracker; mLifecycle = new LifecycleRegistry(mLifecycleOwner); @@ -238,7 +235,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index ca3923f06a13..c565a271bdd7 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.media.session.PlaybackState; 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.util.Log; 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 @@ public class NotificationMediaManager implements Dumpable { 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 @@ public class NotificationMediaManager implements Dumpable { 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 @@ public class NotificationMediaManager implements Dumpable { 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 f5a76f0499e2..000000000000 --- 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 2586e9403e01..732c115571f8 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 @@ public class MediaNotificationProcessor { 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 6cf5c303149c..815cfb39ea2f 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView && 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } 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 58b87cd2f492..73c4b054fd4e 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.annotations.VisibleForTesting; 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder // 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 2535e5ddc3d1..c75cd782c3e2 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi } @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 39f5847ce2a6..562d0ec06a63 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.KeyguardStateController; 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 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue 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 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue 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 33798d680d05..2d760e6fc176 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.Dependency; 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 com.android.systemui.statusbar.policy.UserInfoControllerImpl; 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 @@ public class KeyguardStatusBarView extends RelativeLayout 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 @@ public class KeyguardStatusBarView extends RelativeLayout mStatusIconContainer = findViewById(R.id.statusIcons); loadDimens(); + loadBlockList(); mBatteryController = Dependency.get(BatteryController.class); } @@ -181,6 +184,14 @@ public class KeyguardStatusBarView extends RelativeLayout 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 @@ public class KeyguardStatusBarView extends RelativeLayout 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 8fe9a481ccf6..93b83d3cbcbd 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_ICON 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.R; 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.CallIndicatorI 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 @@ public interface StatusBarIconController { * 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; - - 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); + int TAG_PRIMARY = 0; + + /** */ + 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 @@ public interface StatusBarIconController { * 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 @@ public interface StatusBarIconController { /** * 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 interface StatusBarIconController { } } - 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 @@ public interface StatusBarIconController { 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 @@ public interface StatusBarIconController { 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 @@ public interface StatusBarIconController { 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 6404aea05a4d..75900a2bffa1 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 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu private Context mContext; + /** */ @Inject public StatusBarIconControllerImpl( Context context, @@ -84,6 +85,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu demoModeController.addCallback(this); } + /** */ @Override public void addIconGroup(IconManager group) { mIconGroups.add(group); @@ -101,12 +103,14 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } } + /** */ @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 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu 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 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu * 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 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu setIcon(index, holder); } + /** */ @Override public void setIcon(int index, @NonNull StatusBarIconHolder holder) { boolean isNew = getIcon(index, holder.getTag()) == null; @@ -328,6 +335,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu handleSet(index, holder); } + /** */ @Override public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) { Slot slot = getSlot(slotName); @@ -344,15 +352,18 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } } + /** */ 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 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } } + /** */ @Override public void removeIcon(int index, int tag) { if (getIcon(index, tag) == null) { @@ -384,6 +396,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu 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 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu super.dump(pw); } + /** */ @Override public void onDemoModeStarted() { for (IconManager manager : mIconGroups) { @@ -411,6 +425,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } } + /** */ @Override public void onDemoModeFinished() { for (IconManager manager : mIconGroups) { @@ -420,6 +435,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } } + /** */ @Override public void dispatchDemoCommand(String command, Bundle args) { for (IconManager manager : mIconGroups) { @@ -429,6 +445,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } } + /** */ @Override public List<String> demoCommands() { List<String> s = new ArrayList<>(); @@ -436,6 +453,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu 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 19db02a71777..af342dd31a76 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 @@ public class StatusBarIconHolder { 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 @@ public class StatusBarIconHolder { 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 @@ public class StatusBarIconHolder { return holder; } + /** */ public static StatusBarIconHolder fromWifiIconState(WifiIconState state) { StatusBarIconHolder holder = new StatusBarIconHolder(); holder.mWifiState = state; @@ -63,6 +70,7 @@ public class StatusBarIconHolder { return holder; } + /** */ public static StatusBarIconHolder fromMobileIconState(MobileIconState state) { StatusBarIconHolder holder = new StatusBarIconHolder(); holder.mMobileState = state; @@ -75,7 +83,8 @@ public class StatusBarIconHolder { * 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 525f2205f784..94edd1e0415a 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.app.KeyguardManager; 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 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, boolean removedByUser, int reason) { StatusBarNotificationPresenter.this.onNotificationRemoved( - entry.getKey(), entry.getSbn()); + entry.getKey(), entry.getSbn(), reason); if (removedByUser) { maybeEndAmbientPulse(); } @@ -301,13 +302,14 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, 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/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index 1c7a84a36404..1f4dffa09d86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -65,6 +65,7 @@ import android.util.DisplayMetrics; import androidx.test.filters.SmallTest; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.util.ArrayUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.people.widget.PeopleTileKey; @@ -119,6 +120,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { .setNotificationKey(NOTIFICATION_KEY) .setNotificationContent(NOTIFICATION_CONTENT) .setNotificationDataUri(URI) + .setMessagesCount(1) .build(); private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext, @@ -318,7 +320,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { } @Test - public void testGetLastMessagingStyleMessageNoMessage() { + public void testGetMessagingStyleMessagesNoMessage() { Notification notification = new Notification.Builder(mContext, "test") .setContentTitle("TEST_TITLE") .setContentText("TEST_TEXT") @@ -328,22 +330,23 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { .setNotification(notification) .build(); - Notification.MessagingStyle.Message lastMessage = - PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification()); + List<Notification.MessagingStyle.Message> messages = + PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification()); - assertThat(lastMessage).isNull(); + assertThat(ArrayUtils.isEmpty(messages)).isTrue(); } @Test - public void testGetLastMessagingStyleMessage() { + public void testGetMessagingStyleMessages() { StatusBarNotification sbn = new SbnBuilder() .setNotification(mNotification1) .build(); - Notification.MessagingStyle.Message lastMessage = - PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification()); + List<Notification.MessagingStyle.Message> messages = + PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification()); - assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2); + assertThat(messages.size()).isEqualTo(3); + assertThat(messages.get(0).getText().toString()).isEqualTo(NOTIFICATION_TEXT_2); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java index 39bf06050741..c2e0d6b49a76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java @@ -467,6 +467,9 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility()); + // Has a single message, no count shown. + assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility()); + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, getSizeInDp(R.dimen.required_width_for_large)); mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, @@ -492,6 +495,36 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { } @Test + public void testCreateRemoteViewsWithNotificationTemplateTwoMessages() { + PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder() + .setNotificationDataUri(null) + .setStatuses(Arrays.asList(GAME_STATUS, + NEW_STORY_WITH_AVAILABILITY)) + .setMessagesCount(2).build(); + RemoteViews views = new PeopleTileViewHelper(mContext, + tileWithStatusAndNotification, 0, mOptions).getViews(); + View result = views.apply(mContext, null); + + TextView name = (TextView) result.findViewById(R.id.name); + assertEquals(name.getText(), NAME); + TextView subtext = (TextView) result.findViewById(R.id.subtext); + assertEquals(View.GONE, subtext.getVisibility()); + // Has availability. + View availability = result.findViewById(R.id.availability); + assertEquals(View.VISIBLE, availability.getVisibility()); + // Has person icon. + View personIcon = result.findViewById(R.id.person_icon); + assertEquals(View.VISIBLE, personIcon.getVisibility()); + // Has notification content. + TextView statusContent = (TextView) result.findViewById(R.id.text_content); + assertEquals(statusContent.getText(), NOTIFICATION_CONTENT); + + // Has 2 messages, show count. + assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility()); + } + + + @Test public void testGetBackgroundTextFromMessageNoPunctuation() { String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 4948c2b18746..3595095ba615 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.privacy.logging.PrivacyLogger 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 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @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 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { uiEventLogger, qsTileHost, statusBarIconController, - commandQueue, demoModeController, userTracker, quickQSPanelController, @@ -233,4 +229,4 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `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 e6287e7063d3..aeb5b037be0c 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 @@ package com.android.systemui.statusbar.notification; 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 @@ public class MediaNotificationProcessorTest extends SysuiTestCase { */ 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 @@ public class MediaNotificationProcessorTest extends SysuiTestCase { } @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 @@ public class MediaNotificationProcessorTest extends SysuiTestCase { 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 fbe4d7315baa..000000000000 --- 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 7b7e2d3e34df..f147f1cec9ed 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 androidx.test.filters.SmallTest; 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 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { @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 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { 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 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { } 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 9ac93d92713b..b160d78a0b64 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 class CompanionDeviceManagerService extends SystemService implements Bind } 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind 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 ed2e62513ab3..5f7016e724ef 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -222,10 +222,12 @@ filegroup { 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 @@ filegroup { "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 19858488a917..63639ed0bbf6 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.NetworkRequest; 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.VpnTransportInfo; 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * 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 class ConnectivityService extends IConnectivityManager.Stub public ConnectivityService(Context context) { this(context, getDnsResolver(context), new IpConnectivityLog(), - NetdService.getInstance(), new Dependencies()); + INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), + new Dependencies()); } @VisibleForTesting @@ -1202,7 +1203,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkRanker = new NetworkRanker(); final NetworkRequest defaultInternetRequest = createDefaultRequest(); mDefaultRequest = new NetworkRequestInfo( - defaultInternetRequest, null, + Process.myUid(), defaultInternetRequest, null, new Binder(), NetworkCallback.FLAG_INCLUDE_LOCATION_INFO, null /* attributionTags */); mNetworkRequests.put(defaultInternetRequest, mDefaultRequest); @@ -1408,8 +1409,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (enable) { handleRegisterNetworkRequest(new NetworkRequestInfo( - networkRequest, null, - new Binder(), + Process.myUid(), networkRequest, null, new Binder(), NetworkCallback.FLAG_INCLUDE_LOCATION_INFO, null /* attributionTags */)); } else { @@ -1562,7 +1562,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final int requestId = nri.getActiveRequest() != null ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId; mNetworkInfoBlockingLogs.log(String.format( - "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId())); + "%s %d(%d) on netId %d", action, nri.mAsUid, requestId, net.getNetId())); } /** @@ -2077,6 +2077,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc, int callerUid, String callerPackageName) { if (!checkSettingsPermission()) { + // There is no need to track the effective UID of the request here. If the caller lacks + // the settings permission, the effective UID is the same as the calling ID. nc.setSingleUid(callerUid); } nc.setRequestorUidAndPackageName(callerUid, callerPackageName); @@ -2904,10 +2906,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } pw.println(); - pw.println("NetworkStackClient logs:"); - pw.increaseIndent(); - NetworkStackClient.getInstance().dump(pw); - pw.decreaseIndent(); pw.println(); pw.println("Permission Monitor:"); @@ -5367,6 +5365,8 @@ public class ConnectivityService extends IConnectivityManager.Stub boolean mPendingIntentSent; @Nullable final Messenger mMessenger; + + // Information about the caller that caused this object to be created. @Nullable private final IBinder mBinder; final int mPid; @@ -5374,6 +5374,13 @@ public class ConnectivityService extends IConnectivityManager.Stub final @NetworkCallback.Flag int mCallbackFlags; @Nullable final String mCallingAttributionTag; + + // Effective UID of this request. This is different from mUid when a privileged process + // files a request on behalf of another UID. This UID is used to determine blocked status, + // UID matching, and so on. mUid above is used for permission checks and to enforce the + // maximum limit of registered callbacks per UID. + final int mAsUid; + // In order to preserve the mapping of NetworkRequest-to-callback when apps register // callbacks using a returned NetworkRequest, the original NetworkRequest needs to be // maintained for keying off of. This is only a concern when the original nri @@ -5401,12 +5408,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return (null == uids) ? new ArraySet<>() : uids; } - NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi, - @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), r, pi, callingAttributionTag); + NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, + @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { + this(asUid, Collections.singletonList(r), r, pi, callingAttributionTag); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r, @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { ensureAllNetworkRequestsHaveType(r); @@ -5417,6 +5424,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = null; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); + mAsUid = asUid; mNetworkRequestCounter.incrementCountOrThrow(mUid); /** * Location sensitive data not included in pending intent. Only included in @@ -5426,14 +5434,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mCallingAttributionTag = callingAttributionTag; } - NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, + NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m, @Nullable final IBinder binder, @NetworkCallback.Flag int callbackFlags, @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), r, m, binder, callbackFlags, callingAttributionTag); + this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags, + callingAttributionTag); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r, @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m, @Nullable final IBinder binder, @NetworkCallback.Flag int callbackFlags, @@ -5446,6 +5455,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = binder; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); + mAsUid = asUid; mPendingIntent = null; mNetworkRequestCounter.incrementCountOrThrow(mUid); mCallbackFlags = callbackFlags; @@ -5488,18 +5498,19 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = nri.mBinder; mPid = nri.mPid; mUid = nri.mUid; + mAsUid = nri.mAsUid; mPendingIntent = nri.mPendingIntent; mNetworkRequestCounter.incrementCountOrThrow(mUid); mCallbackFlags = nri.mCallbackFlags; mCallingAttributionTag = nri.mCallingAttributionTag; } - NetworkRequestInfo(@NonNull final NetworkRequest r) { - this(Collections.singletonList(r)); + NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) { + this(asUid, Collections.singletonList(r)); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r) { - this(r, r.get(0), null /* pi */, null /* callingAttributionTag */); + NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r) { + this(asUid, r, r.get(0), null /* pi */, null /* callingAttributionTag */); } // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer @@ -5535,9 +5546,10 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String toString() { - return "uid/pid:" + mUid + "/" + mPid + " active request Id: " + final String asUidString = (mAsUid == mUid) ? "" : " asUid: " + mAsUid; + return "uid/pid:" + mUid + "/" + mPid + asUidString + " activeRequest: " + (mActiveRequest == null ? null : mActiveRequest.requestId) - + " callback request Id: " + + " callbackRequest: " + mNetworkRequestForCallback.requestId + " " + mRequests + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent) @@ -5638,7 +5650,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities, + public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities, int reqTypeInt, Messenger messenger, int timeoutMs, IBinder binder, int legacyType, int callbackFlags, @NonNull String callingPackageName, @Nullable String callingAttributionTag) { @@ -5650,6 +5662,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities; final int callingUid = mDeps.getCallingUid(); + // Privileged callers can track the default network of another UID by passing in a UID. + if (asUid != Process.INVALID_UID) { + enforceSettingsPermission(); + } else { + asUid = callingUid; + } final NetworkRequest.Type reqType; try { reqType = NetworkRequest.Type.values()[reqTypeInt]; @@ -5659,10 +5677,10 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (reqType) { case TRACK_DEFAULT: // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities} - // is unused and will be replaced by ones appropriate for the caller. - // This allows callers to keep track of the default network for their app. + // is unused and will be replaced by ones appropriate for the UID (usually, the + // calling app). This allows callers to keep track of the default network. networkCapabilities = copyDefaultNetworkCapabilitiesForUid( - defaultNc, callingUid, callingPackageName); + defaultNc, asUid, callingUid, callingPackageName); enforceAccessPermission(); break; case TRACK_SYSTEM_DEFAULT: @@ -5714,7 +5732,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); final NetworkRequestInfo nri = getNriToRegister( - networkRequest, messenger, binder, callbackFlags, callingAttributionTag); + asUid, networkRequest, messenger, binder, callbackFlags, + callingAttributionTag); if (DBG) log("requestNetwork for " + nri); // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were @@ -5741,25 +5760,27 @@ public class ConnectivityService extends IConnectivityManager.Stub * requests registered to track the default request. If there is currently a per-app default * tracking the app requestor, then we need to create a version of this nri that mirrors that of * the tracking per-app default so that callbacks are sent to the app requestor appropriately. + * @param asUid the uid on behalf of which to file the request. Different from requestorUid + * when a privileged caller is tracking the default network for another uid. * @param nr the network request for the nri. * @param msgr the messenger for the nri. * @param binder the binder for the nri. * @param callingAttributionTag the calling attribution tag for the nri. * @return the nri to register. */ - private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr, + private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr, @Nullable final Messenger msgr, @Nullable final IBinder binder, @NetworkCallback.Flag int callbackFlags, @Nullable String callingAttributionTag) { final List<NetworkRequest> requests; if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) { requests = copyDefaultNetworkRequestsForUid( - nr.getRequestorUid(), nr.getRequestorPackageName()); + asUid, nr.getRequestorUid(), nr.getRequestorPackageName()); } else { requests = Collections.singletonList(nr); } return new NetworkRequestInfo( - requests, nr, msgr, binder, callbackFlags, callingAttributionTag); + asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag); } private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities, @@ -5840,8 +5861,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.REQUEST); - NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, operation, callingAttributionTag); + NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation, + callingAttributionTag); if (DBG) log("pendingRequest for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT, nri)); @@ -5908,7 +5929,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, messenger, binder, callbackFlags, + new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags, callingAttributionTag); if (VDBG) log("listenForNetwork for " + nri); @@ -5933,8 +5954,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); - NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, operation, callingAttributionTag); + NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation, + callingAttributionTag); if (VDBG) log("pendingListenForNetwork for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); @@ -6084,33 +6105,37 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Get a copy of the network requests of the default request that is currently tracking the * given uid. + * @param asUid the uid on behalf of which to file the request. Different from requestorUid + * when a privileged caller is tracking the default network for another uid. * @param requestorUid the uid to check the default for. * @param requestorPackageName the requestor's package name. * @return a copy of the default's NetworkRequest that is tracking the given uid. */ @NonNull private List<NetworkRequest> copyDefaultNetworkRequestsForUid( - @NonNull final int requestorUid, @NonNull final String requestorPackageName) { + final int asUid, final int requestorUid, @NonNull final String requestorPackageName) { return copyNetworkRequestsForUid( - getDefaultRequestTrackingUid(requestorUid).mRequests, - requestorUid, requestorPackageName); + getDefaultRequestTrackingUid(asUid).mRequests, + asUid, requestorUid, requestorPackageName); } /** * Copy the given nri's NetworkRequest collection. * @param requestsToCopy the NetworkRequest collection to be copied. + * @param asUid the uid on behalf of which to file the request. Different from requestorUid + * when a privileged caller is tracking the default network for another uid. * @param requestorUid the uid to set on the copied collection. * @param requestorPackageName the package name to set on the copied collection. * @return the copied NetworkRequest collection. */ @NonNull private List<NetworkRequest> copyNetworkRequestsForUid( - @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid, - @NonNull final String requestorPackageName) { + @NonNull final List<NetworkRequest> requestsToCopy, final int asUid, + final int requestorUid, @NonNull final String requestorPackageName) { final List<NetworkRequest> requests = new ArrayList<>(); for (final NetworkRequest nr : requestsToCopy) { requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid( - nr.networkCapabilities, requestorUid, requestorPackageName), + nr.networkCapabilities, asUid, requestorUid, requestorPackageName), nr.legacyType, nextNetworkRequestId(), nr.type)); } return requests; @@ -6118,17 +6143,17 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid( - @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid, - @NonNull final String requestorPackageName) { + @NonNull final NetworkCapabilities netCapToCopy, final int asUid, + final int requestorUid, @NonNull final String requestorPackageName) { // These capabilities are for a TRACK_DEFAULT callback, so: // 1. Remove NET_CAPABILITY_VPN, because it's (currently!) the only difference between // mDefaultRequest and a per-UID default request. // TODO: stop depending on the fact that these two unrelated things happen to be the same - // 2. Always set the UIDs to mAsUid. restrictRequestUidsForCallerAndSetRequestorInfo will + // 2. Always set the UIDs to asUid. restrictRequestUidsForCallerAndSetRequestorInfo will // not do this in the case of a privileged application. final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy); netCap.removeCapability(NET_CAPABILITY_NOT_VPN); - netCap.setSingleUid(requestorUid); + netCap.setSingleUid(asUid); restrictRequestUidsForCallerAndSetRequestorInfo( netCap, requestorUid, requestorPackageName); return netCap; @@ -8034,9 +8059,9 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean metered = nai.networkCapabilities.isMetered(); boolean blocked; - blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges); + blocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges); blocked |= NetworkPolicyManager.isUidBlocked( - mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE), metered); + mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE), metered); callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); } @@ -8064,12 +8089,12 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequestInfo nri = mNetworkRequests.get(nr); final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked; - oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges); + oldVpnBlocked = isUidBlockedByVpn(nri.mAsUid, oldBlockedUidRanges); newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges) - ? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges) + ? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges) : oldVpnBlocked; - final int blockedReasons = mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE); + final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE); oldBlocked = oldVpnBlocked || NetworkPolicyManager.isUidBlocked( blockedReasons, oldMetered); newBlocked = newVpnBlocked || NetworkPolicyManager.isUidBlocked( @@ -8104,7 +8129,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest nr = nai.requestAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); - if (nri != null && nri.mUid == uid) { + if (nri != null && nri.mAsUid == uid) { callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg); } } @@ -8869,7 +8894,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // nri is not bound to the death of callback. Instead, callback.bindToDeath() is set in // handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the // callback's binder death. - final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId); + final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId); final ConnectivityDiagnosticsCallbackInfo cbInfo = new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName); @@ -9353,7 +9378,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities)); nrs.add(createDefaultRequest()); setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids())); - final NetworkRequestInfo nri = new NetworkRequestInfo(nrs); + final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs); result.add(nri); } return result; @@ -9524,7 +9549,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Include this nri if it will be tracked by the new per-app default requests. final boolean isNriGoingToBeTracked = - getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest; + getDefaultRequestTrackingUid(nri.mAsUid) != mDefaultRequest; if (isNriGoingToBeTracked) { defaultCallbackRequests.add(nri); } @@ -9546,7 +9571,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>(); for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) { final NetworkRequestInfo trackingNri = - getDefaultRequestTrackingUid(callbackRequest.mUid); + getDefaultRequestTrackingUid(callbackRequest.mAsUid); // If this nri is not being tracked, the change it back to an untracked nri. if (trackingNri == mDefaultRequest) { @@ -9556,12 +9581,12 @@ public class ConnectivityService extends IConnectivityManager.Stub continue; } - final String requestorPackageName = - callbackRequest.mRequests.get(0).getRequestorPackageName(); + final NetworkRequest request = callbackRequest.mRequests.get(0); callbackRequestsToRegister.add(new NetworkRequestInfo( callbackRequest, copyNetworkRequestsForUid( - trackingNri.mRequests, callbackRequest.mUid, requestorPackageName))); + trackingNri.mRequests, callbackRequest.mAsUid, + callbackRequest.mUid, request.getRequestorPackageName()))); } return callbackRequestsToRegister; } @@ -9665,7 +9690,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ranges.add(new UidRange(uid, uid)); } setNetworkRequestUids(requests, ranges); - return new NetworkRequestInfo(requests); + return new NetworkRequestInfo(Process.myUid(), requests); } private NetworkRequest createUnmeteredNetworkRequest() { diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index f04af8bbf1a0..0c3d88445747 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -448,7 +448,7 @@ public final class DropBoxManagerService extends SystemService { 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 f5662772f59f..09873f4db045 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.NetworkProvider; 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 @@ class TestNetworkService extends ITestNetworkManager.Stub { mHandler = new Handler(mHandlerThread.getLooper()); mContext = Objects.requireNonNull(context, "missing Context"); - mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance"); + mNetd = Objects.requireNonNull( + INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), + "could not get netd instance"); mCm = mContext.getSystemService(ConnectivityManager.class); mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(), TEST_NETWORK_PROVIDER_NAME); diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 051cd9907bee..77cec78ff9a8 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -820,8 +820,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { final IBinder cbBinder = callback.asBinder(); final VcnStatusCallbackInfo cbInfo = - new VcnStatusCallbackInfo( - subGroup, callback, opPkgName, mDeps.getBinderCallingUid()); + new VcnStatusCallbackInfo(subGroup, callback, opPkgName, callingUid); try { cbBinder.linkToDeath(cbInfo, 0 /* flags */); diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 963664149b38..211999f43183 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -35,11 +35,11 @@ import android.os.ServiceDebugInfo; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.sysprop.WatchdogProperties; import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import android.sysprop.WatchdogProperties; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ZygoteConnectionConstants; @@ -56,9 +56,9 @@ import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.concurrent.TimeUnit; import java.util.HashSet; import java.util.List; +import java.util.concurrent.TimeUnit; /** This class calls its monitor every minute. Killing this process if they don't return **/ public class Watchdog { @@ -688,7 +688,7 @@ public class Watchdog { if (mActivity != null) { mActivity.addErrorToDropBox( "watchdog", null, "system_server", null, null, null, - subject, report.toString(), stack, null); + subject, report.toString(), stack, null, null, null); } FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c4548a3070a7..321e3b14896c 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.PropertyInvalidatedCache; 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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); @@ -7703,9 +7706,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName, ApplicationErrorReport.CrashInfo crashInfo) { - boolean isIncremental = false; float loadingProgress = 1; - long millisSinceOldestPendingRead = 0; + IncrementalMetrics incrementalMetrics = null; // Notify package manager service to possibly update package state if (r != null && r.info != null && r.info.packageName != null) { final String codePath = r.info.getCodePath(); @@ -7716,8 +7718,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (incrementalStatesInfo != null) { loadingProgress = incrementalStatesInfo.getProgress(); } - isIncremental = IncrementalManager.isIncrementalPath(codePath); - if (isIncremental) { + if (IncrementalManager.isIncrementalPath(codePath)) { // Report in the main log about the incremental package Slog.e(TAG, "App crashed on incremental package " + r.info.packageName + " which is " + ((int) (loadingProgress * 100)) + "% loaded."); @@ -7726,8 +7727,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (incrementalService != null) { final IncrementalManager incrementalManager = new IncrementalManager( IIncrementalService.Stub.asInterface(incrementalService)); - IncrementalMetrics metrics = incrementalManager.getMetrics(codePath); - millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead(); + incrementalMetrics = incrementalManager.getMetrics(codePath); } } } @@ -7757,7 +7757,9 @@ public class ActivityManagerService extends IActivityManager.Stub processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER : (r != null) ? r.getProcessClassEnum() : ServerProtoEnums.ERROR_SOURCE_UNKNOWN, - isIncremental, loadingProgress, millisSinceOldestPendingRead + incrementalMetrics != null /* isIncremental */, loadingProgress, + incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead() + : -1 ); final int relaunchReason = r == null ? RELAUNCH_REASON_NONE @@ -7770,7 +7772,8 @@ public class ActivityManagerService extends IActivityManager.Stub } addErrorToDropBox( - eventType, r, processName, null, null, null, null, null, null, crashInfo); + eventType, r, processName, null, null, null, null, null, null, crashInfo, + new Float(loadingProgress), incrementalMetrics); mAppErrors.crashApplication(r, crashInfo); } @@ -7952,7 +7955,8 @@ public class ActivityManagerService extends IActivityManager.Stub FrameworkStatsLog.write(FrameworkStatsLog.WTF_OCCURRED, callingUid, tag, processName, callingPid, (r != null) ? r.getProcessClassEnum() : 0); - addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo); + addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo, + null, null); return r; } @@ -7977,7 +7981,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (Pair<String, ApplicationErrorReport.CrashInfo> p = list.poll(); p != null; p = list.poll()) { addErrorToDropBox("wtf", proc, "system_server", null, null, null, p.first, null, null, - p.second); + p.second, null, null); } } @@ -8066,12 +8070,15 @@ public class ActivityManagerService extends IActivityManager.Stub * @param report in long form describing the error, null if absent * @param dataFile text file to include in the report, null if none * @param crashInfo giving an application stack trace, null if absent + * @param loadingProgress the loading progress of an installed package, range in [0, 1]. + * @param incrementalMetrics metrics for apps installed on Incremental. */ public void addErrorToDropBox(String eventType, ProcessRecord process, String processName, String activityShortComponentName, String parentShortComponentName, ProcessRecord parentProcess, String subject, final String report, final File dataFile, - final ApplicationErrorReport.CrashInfo crashInfo) { + final ApplicationErrorReport.CrashInfo crashInfo, + @Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics) { // NOTE -- this must never acquire the ActivityManagerService lock, // otherwise the watchdog may be prevented from resetting the system. @@ -8132,6 +8139,18 @@ public class ActivityManagerService extends IActivityManager.Stub if (crashInfo != null && crashInfo.crashTag != null && !crashInfo.crashTag.isEmpty()) { sb.append("Crash-Tag: ").append(crashInfo.crashTag).append("\n"); } + if (loadingProgress != null) { + sb.append("Loading-Progress: ").append(loadingProgress.floatValue()).append("\n"); + } + if (incrementalMetrics != null) { + sb.append("Incremental: Yes").append("\n"); + final long millisSinceOldestPendingRead = + incrementalMetrics.getMillisSinceOldestPendingRead(); + if (millisSinceOldestPendingRead > 0) { + sb.append("Millis-Since-Oldest-Pending-Read: ").append( + millisSinceOldestPendingRead).append("\n"); + } + } sb.append("\n"); // Do the rest in a worker thread to avoid blocking the caller on I/O diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 31ea14a73409..074e8fe42016 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1627,7 +1627,7 @@ public class AppProfiler { dropBuilder.append(catSw.toString()); FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED); mService.addErrorToDropBox("lowmem", null, "system_server", null, - null, null, tag.toString(), dropBuilder.toString(), null, null); + null, null, tag.toString(), dropBuilder.toString(), null, null, null, null); synchronized (mService) { long now = SystemClock.uptimeMillis(); if (mLastMemUsageReportTime < now) { diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java index d8d6528621d2..70a54cfdafa5 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 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub { * 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 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub { * 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 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub { 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/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 93f30cc8ac5e..ab4a2d59ed90 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -300,9 +300,8 @@ class ProcessErrorStateRecord { } // Check if package is still being loaded - boolean isIncremental = false; float loadingProgress = 1; - long millisSinceOldestPendingRead = 0; + IncrementalMetrics incrementalMetrics = null; final PackageManagerInternal packageManagerInternal = mService.getPackageManagerInternal(); if (aInfo != null && aInfo.packageName != null) { IncrementalStatesInfo incrementalStatesInfo = @@ -312,8 +311,7 @@ class ProcessErrorStateRecord { loadingProgress = incrementalStatesInfo.getProgress(); } final String codePath = aInfo.getCodePath(); - isIncremental = IncrementalManager.isIncrementalPath(codePath); - if (isIncremental) { + if (IncrementalManager.isIncrementalPath(codePath)) { // Report in the main log that the incremental package is still loading Slog.e(TAG, "App crashed on incremental package " + aInfo.packageName + " which is " + ((int) (loadingProgress * 100)) + "% loaded."); @@ -322,8 +320,7 @@ class ProcessErrorStateRecord { if (incrementalService != null) { final IncrementalManager incrementalManager = new IncrementalManager( IIncrementalService.Stub.asInterface(incrementalService)); - IncrementalMetrics metrics = incrementalManager.getMetrics(codePath); - millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead(); + incrementalMetrics = incrementalManager.getMetrics(codePath); } } } @@ -345,7 +342,7 @@ class ProcessErrorStateRecord { info.append("Parent: ").append(parentShortComponentName).append("\n"); } - if (isIncremental) { + if (incrementalMetrics != null) { // Report in the main log about the incremental package info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n"); } @@ -434,12 +431,14 @@ class ProcessErrorStateRecord { : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND, mApp.getProcessClassEnum(), (mApp.info != null) ? mApp.info.packageName : "", - isIncremental, loadingProgress, millisSinceOldestPendingRead); + incrementalMetrics != null /* isIncremental */, loadingProgress, + incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead() + : -1); final ProcessRecord parentPr = parentProcess != null ? (ProcessRecord) parentProcess.mOwner : null; mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName, parentShortComponentName, parentPr, annotation, report.toString(), tracesFile, - null); + null, new Float(loadingProgress), incrementalMetrics); if (mApp.getWindowProcessController().appNotResponding(info.toString(), () -> { diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index c6824d16cffb..ebe9d96ee1f6 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -69,6 +69,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -101,6 +102,7 @@ public final class AppHibernationService extends SystemService { private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>(); private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore; private final Injector mInjector; + private final Executor mBackgroundExecutor; @VisibleForTesting boolean mIsServiceEnabled; @@ -126,6 +128,7 @@ public final class AppHibernationService extends SystemService { mIActivityManager = injector.getActivityManager(); mUserManager = injector.getUserManager(); mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); + mBackgroundExecutor = injector.getBackgroundExecutor(); mInjector = injector; final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); @@ -147,11 +150,13 @@ public final class AppHibernationService extends SystemService { @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { - List<GlobalLevelState> states = - mGlobalLevelHibernationDiskStore.readHibernationStates(); - synchronized (mLock) { - initializeGlobalHibernationStates(states); - } + mBackgroundExecutor.execute(() -> { + List<GlobalLevelState> states = + mGlobalLevelHibernationDiskStore.readHibernationStates(); + synchronized (mLock) { + initializeGlobalHibernationStates(states); + } + }); } if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { mIsServiceEnabled = isAppHibernationEnabled(); @@ -170,16 +175,15 @@ public final class AppHibernationService extends SystemService { * @return true if package is hibernating for the user */ boolean isHibernatingForUser(String packageName, int userId) { - if (!checkHibernationEnabled("isHibernatingForUser")) { + String methodName = "isHibernatingForUser"; + if (!checkHibernationEnabled(methodName)) { return false; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); - userId = handleIncomingUser(userId, "isHibernating"); - if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user " - + userId); + userId = handleIncomingUser(userId, methodName); + if (!checkUserStatesExist(userId, methodName)) { return false; } synchronized (mLock) { @@ -225,16 +229,15 @@ public final class AppHibernationService extends SystemService { * @param isHibernating new hibernation state */ void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { - if (!checkHibernationEnabled("setHibernatingForUser")) { + String methodName = "setHibernatingForUser"; + if (!checkHibernationEnabled(methodName)) { return; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); - userId = handleIncomingUser(userId, "setHibernating"); - if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user " - + userId); + userId = handleIncomingUser(userId, methodName); + if (!checkUserStatesExist(userId, methodName)) { return; } synchronized (mLock) { @@ -298,16 +301,15 @@ public final class AppHibernationService extends SystemService { */ @NonNull List<String> getHibernatingPackagesForUser(int userId) { ArrayList<String> hibernatingPackages = new ArrayList<>(); - if (!checkHibernationEnabled("getHibernatingPackagesForUser")) { + String methodName = "getHibernatingPackagesForUser"; + if (!checkHibernationEnabled(methodName)) { return hibernatingPackages; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); - userId = handleIncomingUser(userId, "getHibernatingPackagesForUser"); - if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - Slog.w(TAG, "Attempt to get hibernating packages for a stopped or nonexistent user " - + userId); + userId = handleIncomingUser(userId, methodName); + if (!checkUserStatesExist(userId, methodName)) { return hibernatingPackages; } synchronized (mLock) { @@ -468,10 +470,15 @@ public final class AppHibernationService extends SystemService { HibernationStateDiskStore<UserLevelState> diskStore = mInjector.getUserLevelDiskStore(userId); mUserDiskStores.put(userId, diskStore); - List<UserLevelState> storedStates = diskStore.readHibernationStates(); - synchronized (mLock) { - initializeUserHibernationStates(userId, storedStates); - } + mBackgroundExecutor.execute(() -> { + List<UserLevelState> storedStates = diskStore.readHibernationStates(); + synchronized (mLock) { + // Ensure user hasn't stopped in the time to execute. + if (mUserManager.isUserUnlockingOrUnlocked(userId)) { + initializeUserHibernationStates(userId, storedStates); + } + } + }); } @Override @@ -541,6 +548,20 @@ public final class AppHibernationService extends SystemService { } } + private boolean checkUserStatesExist(int userId, String methodName) { + if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { + Slog.e(TAG, String.format( + "Attempt to call %s on stopped or nonexistent user %d", methodName, userId)); + return false; + } + if (!mUserStates.contains(userId)) { + Slog.w(TAG, String.format( + "Attempt to call %s before states have been read from disk", methodName)); + return false; + } + return true; + } + private boolean checkHibernationEnabled(String methodName) { if (!mIsServiceEnabled) { Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName)); @@ -711,6 +732,8 @@ public final class AppHibernationService extends SystemService { UserManager getUserManager(); + Executor getBackgroundExecutor(); + HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore(); HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId); @@ -749,6 +772,11 @@ public final class AppHibernationService extends SystemService { } @Override + public Executor getBackgroundExecutor() { + return mScheduledExecutorService; + } + + @Override public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME); return new HibernationStateDiskStore<>( diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java index c83659d2ff56..24cf43339847 100644 --- a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java +++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java @@ -109,6 +109,7 @@ class HibernationStateDiskStore<T> { * @return the parsed list of hibernation states, null if file does not exist */ @Nullable + @WorkerThread List<T> readHibernationStates() { synchronized (this) { if (!mHibernationFile.exists()) { diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 81ce2d535237..3cfaaf7c23e0 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 @@ public abstract class BaseClientMonitor extends LoggableMonitor 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 20c25c35535a..6c480f134279 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 @@ public class BiometricScheduler { 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 @@ public class BiometricScheduler { } } - @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 @@ public class BiometricScheduler { 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 @@ public class BiometricScheduler { 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 000000000000..8b9be83bc8c3 --- /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 000000000000..62cd673babac --- /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 000000000000..c0ea2b3f8b93 --- /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 ca29057c9888..fdc3bb172a37 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 c6f39aa07f26..3eb475921304 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 @@ public class Sensor { }); } + @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 1b5def6c7063..bcca69b3ad35 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 5631647816ec..d843bc94455c 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 @@ class Sensor { 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 71565301e3ed..41bc0b928d41 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -106,15 +106,29 @@ class HostClipboardMonitor implements Runnable { 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) {} + pipe.write(createOpenHandshake()); + mPipe = pipe; + return true; + } catch (IOException ignore) { + pipe.close(); + } + } catch (IOException ignore) { + } + return false; + } + + private void closePipe() { + try { + final RandomAccessFile pipe = mPipe; mPipe = null; + if (pipe != null) { + pipe.close(); + } + } catch (IOException ignore) { } } @@ -129,8 +143,7 @@ class HostClipboardMonitor implements Runnable { // 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(); @@ -140,10 +153,7 @@ class HostClipboardMonitor implements Runnable { mHostClipboardCallback.onHostClipboardUpdated( new String(receivedData)); } catch (IOException e) { - try { - mPipe.close(); - } catch (IOException ee) {} - mPipe = null; + closePipe(); } catch (InterruptedException e) {} } } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 74e4ae7f5bf3..acf39f05a541 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_INTERVAL; 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 @@ public class KeepaliveTracker { } } 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 000000000000..57e3dcdf0d73 --- /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 c480594b8c60..73f34751731e 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_IP; 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 @@ public class TcpKeepaliveController { } // 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 @@ public class TcpKeepaliveController { 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 @@ public class TcpKeepaliveController { 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 @@ public class TcpKeepaliveController { 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 1b39d848097d..bd9a52010b96 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1859,22 +1859,13 @@ public class Vpn { /** * 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 aaf9cbc168af..1f4606104ab8 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 class SyncJobService extends JobService { 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 @@ public class SyncJobService extends JobService { 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 @@ public class SyncJobService extends JobService { 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/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index d0e7e459e7f2..58308d8f1343 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -250,7 +250,19 @@ final class Constants { @Retention(RetentionPolicy.SOURCE) @IntDef({ - ABORT_NO_ERROR, + NOT_HANDLED, + HANDLED, + ABORT_UNRECOGNIZED_OPCODE, + ABORT_NOT_IN_CORRECT_MODE, + ABORT_CANNOT_PROVIDE_SOURCE, + ABORT_INVALID_OPERAND, + ABORT_REFUSED, + ABORT_UNABLE_TO_DETERMINE, + }) + public @interface HandleMessageResult {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ ABORT_UNRECOGNIZED_OPCODE, ABORT_NOT_IN_CORRECT_MODE, ABORT_CANNOT_PROVIDE_SOURCE, @@ -260,8 +272,11 @@ final class Constants { }) public @interface AbortReason {} - // Internal abort error code. It's the same as success. - static final int ABORT_NO_ERROR = -1; + // Indicates that a message was not handled, but could be handled by another local device. + // If no local devices handle the message, we send <Feature Abort>[Unrecognized Opcode]. + static final int NOT_HANDLED = -2; + // Indicates that a message has been handled successfully; no feature abort needed. + static final int HANDLED = -1; // Constants related to operands of HDMI CEC commands. // Refer to CEC Table 29 in HDMI Spec v1.4b. // [Abort Reason] diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 1643ec162bc2..ad2ef2a2b665 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -565,19 +565,24 @@ final class HdmiCecController { } @ServiceThreadOnly - private void onReceiveCommand(HdmiCecMessage message) { + @VisibleForTesting + void onReceiveCommand(HdmiCecMessage message) { assertRunOnServiceThread(); - if ((isAcceptableAddress(message.getDestination()) - || !mService.isAddressAllocated()) - && mService.handleCecCommand(message)) { + if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) { return; } - // Not handled message, so we will reply it with <Feature Abort>. - maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + @Constants.HandleMessageResult int messageState = mService.handleCecCommand(message); + if (messageState == Constants.NOT_HANDLED) { + // Message was not handled + maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + } else if (messageState != Constants.HANDLED) { + // Message handler wants to send a feature abort + maySendFeatureAbortCommand(message, messageState); + } } @ServiceThreadOnly - void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) { + void maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason) { assertRunOnServiceThread(); // Swap the source and the destination. int src = message.getDestination(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index bdc4e66cf7f9..505e743ed9a1 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -248,11 +248,13 @@ abstract class HdmiCecLocalDevice { * @return true if consumed a message; otherwise, return false. */ @ServiceThreadOnly - boolean dispatchMessage(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int dispatchMessage(HdmiCecMessage message) { assertRunOnServiceThread(); int dest = message.getDestination(); if (dest != mAddress && dest != Constants.ADDR_BROADCAST) { - return false; + return Constants.NOT_HANDLED; } // Cache incoming message if it is included in the list of cacheable opcodes. mCecMessageCache.cacheMessage(message); @@ -260,10 +262,11 @@ abstract class HdmiCecLocalDevice { } @ServiceThreadOnly - protected final boolean onMessage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected final int onMessage(HdmiCecMessage message) { assertRunOnServiceThread(); if (dispatchMessageToAction(message)) { - return true; + return Constants.HANDLED; } switch (message.getOpcode()) { case Constants.MESSAGE_ACTIVE_SOURCE: @@ -357,7 +360,7 @@ abstract class HdmiCecLocalDevice { case Constants.MESSAGE_GIVE_FEATURES: return handleGiveFeatures(message); default: - return false; + return Constants.NOT_HANDLED; } } @@ -375,7 +378,8 @@ abstract class HdmiCecLocalDevice { } @ServiceThreadOnly - protected boolean handleGivePhysicalAddress(@Nullable SendMessageCallback callback) { + @Constants.HandleMessageResult + protected int handleGivePhysicalAddress(@Nullable SendMessageCallback callback) { assertRunOnServiceThread(); int physicalAddress = mService.getPhysicalAddress(); @@ -383,76 +387,83 @@ abstract class HdmiCecLocalDevice { HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, physicalAddress, mDeviceType); mService.sendCecCommand(cecMessage, callback); - return true; + return Constants.HANDLED; } @ServiceThreadOnly - protected boolean handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) { + @Constants.HandleMessageResult + protected int handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) { assertRunOnServiceThread(); int vendorId = mService.getVendorId(); HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, vendorId); mService.sendCecCommand(cecMessage, callback); - return true; + return Constants.HANDLED; } @ServiceThreadOnly - protected boolean handleGetCecVersion(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGetCecVersion(HdmiCecMessage message) { assertRunOnServiceThread(); int version = mService.getCecVersion(); HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion( message.getDestination(), message.getSource(), version); mService.sendCecCommand(cecMessage); - return true; + return Constants.HANDLED; } @ServiceThreadOnly - private boolean handleCecVersion() { + @Constants.HandleMessageResult + protected int handleCecVersion() { assertRunOnServiceThread(); // Return true to avoid <Feature Abort> responses. Cec Version is tracked in HdmiCecNetwork. - return true; + return Constants.HANDLED; } @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleInactiveSource(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleInactiveSource(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestActiveSource(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleGetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); - // 'return false' will cause to reply with <Feature Abort>. - return false; + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleSetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); Slog.w(TAG, "Only Playback device can handle <Set Menu Language>:" + message.toString()); - // 'return false' will cause to reply with <Feature Abort>. - return false; + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleGiveOsdName(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveOsdName(HdmiCecMessage message) { assertRunOnServiceThread(); // Note that since this method is called after logical address allocation is done, // mDeviceInfo should not be null. buildAndSendSetOsdName(message.getSource()); - return true; + return Constants.HANDLED; } protected void buildAndSendSetOsdName(int dest) { @@ -475,18 +486,21 @@ abstract class HdmiCecLocalDevice { // Audio System device with no Playback device type // needs to refactor this function if it's also a switch - protected boolean handleRoutingChange(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } // Audio System device with no Playback device type // needs to refactor this function if it's also a switch - protected boolean handleRoutingInformation(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRoutingInformation(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @CallSuper - protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportPhysicalAddress(HdmiCecMessage message) { // <Report Physical Address> is also handled in HdmiCecNetwork to update the local network // state @@ -495,7 +509,7 @@ abstract class HdmiCecLocalDevice { // Ignore if [Device Discovery Action] is going on. if (hasAction(DeviceDiscoveryAction.class)) { Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); - return true; + return Constants.HANDLED; } HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address); @@ -506,63 +520,77 @@ abstract class HdmiCecLocalDevice { HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); } - return true; + return Constants.HANDLED; } - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSystemAudioModeRequest(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleTerminateArc(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTerminateArc(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleInitiateArc(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleInitiateArc(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRequestArcInitiate(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestArcInitiate(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRequestArcTermination(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestArcTermination(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportArcInitiate(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportArcInitiate(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportArcTermination(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportArcTermination(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportAudioStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportAudioStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleGiveAudioStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleGiveAudioStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportShortAudioDescriptor(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportShortAudioDescriptor(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @Constants.RcProfile @@ -572,13 +600,14 @@ abstract class HdmiCecLocalDevice { protected abstract List<Integer> getDeviceFeatures(); - protected boolean handleGiveFeatures(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveFeatures(HdmiCecMessage message) { if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) { - return false; + return Constants.ABORT_UNRECOGNIZED_OPCODE; } reportFeatures(); - return true; + return Constants.HANDLED; } protected void reportFeatures() { @@ -598,32 +627,34 @@ abstract class HdmiCecLocalDevice { } @ServiceThreadOnly - protected boolean handleStandby(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleStandby(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #12 if (mService.isControlEnabled() && !mService.isProhibitMode() && mService.isPowerOnOrTransient()) { mService.standby(); - return true; + return Constants.HANDLED; } - return false; + return Constants.ABORT_NOT_IN_CORRECT_MODE; } @ServiceThreadOnly - protected boolean handleUserControlPressed(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleUserControlPressed(HdmiCecMessage message) { assertRunOnServiceThread(); mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) { mService.standby(); - return true; + return Constants.HANDLED; } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) { mService.wakeUp(); - return true; + return Constants.HANDLED; } else if (mService.getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_DISABLED && isVolumeOrMuteCommand( message)) { - return false; + return Constants.ABORT_REFUSED; } if (isPowerOffOrToggleCommand(message) || isPowerOnOrToggleCommand(message)) { @@ -631,7 +662,7 @@ abstract class HdmiCecLocalDevice { // keycode to Android keycode. // Do not <Feature Abort> as the local device should already be in the correct power // state. - return true; + return Constants.HANDLED; } final long downTime = SystemClock.uptimeMillis(); @@ -653,15 +684,15 @@ abstract class HdmiCecLocalDevice { mHandler.sendMessageDelayed( Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT), FOLLOWER_SAFETY_TIMEOUT); - return true; + return Constants.HANDLED; } - mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); - return true; + return Constants.ABORT_INVALID_OPERAND; } @ServiceThreadOnly - protected boolean handleUserControlReleased() { + @Constants.HandleMessageResult + protected int handleUserControlReleased() { assertRunOnServiceThread(); mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); mLastKeyRepeatCount = 0; @@ -670,7 +701,7 @@ abstract class HdmiCecLocalDevice { injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0); mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; } - return true; + return Constants.HANDLED; } static void injectKeyEvent(long time, int action, int keycode, int repeat) { @@ -717,38 +748,45 @@ abstract class HdmiCecLocalDevice { || params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); } - protected boolean handleTextViewOn(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTextViewOn(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleImageViewOn(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleImageViewOn(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleSetStreamPath(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSetStreamPath(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveDevicePowerStatus(HdmiCecMessage message) { mService.sendCecCommand( HdmiCecMessageBuilder.buildReportPowerStatus( mAddress, message.getSource(), mService.getPowerStatus())); - return true; + return Constants.HANDLED; } - protected boolean handleMenuRequest(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleMenuRequest(HdmiCecMessage message) { // Always report menu active to receive Remote Control. mService.sendCecCommand( HdmiCecMessageBuilder.buildReportMenuStatus( mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED)); - return true; + return Constants.HANDLED; } - protected boolean handleMenuStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleMenuStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleVendorCommand(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleVendorCommand(HdmiCecMessage message) { if (!mService.invokeVendorCommandListenersOnReceived( mDeviceType, message.getSource(), @@ -757,57 +795,64 @@ abstract class HdmiCecLocalDevice { false)) { // Vendor command listener may not have been registered yet. Respond with // <Feature Abort> [Refused] so that the sender can try again later. - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; + return Constants.HANDLED; } - protected boolean handleVendorCommandWithId(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleVendorCommandWithId(HdmiCecMessage message) { byte[] params = message.getParams(); int vendorId = HdmiUtils.threeBytesToInt(params); if (vendorId == mService.getVendorId()) { if (!mService.invokeVendorCommandListenersOnReceived( mDeviceType, message.getSource(), message.getDestination(), params, true)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } } else if (message.getDestination() != Constants.ADDR_BROADCAST && message.getSource() != Constants.ADDR_UNREGISTERED) { Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>"); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + return Constants.ABORT_UNRECOGNIZED_OPCODE; } else { Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); } - return true; + return Constants.HANDLED; } protected void sendStandby(int deviceId) { // Do nothing. } - protected boolean handleSetOsdName(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetOsdName(HdmiCecMessage message) { // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state - return true; + return Constants.HANDLED; } - protected boolean handleRecordTvScreen(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRecordTvScreen(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleTimerClearedStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTimerClearedStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportPowerStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportPowerStatus(HdmiCecMessage message) { // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state - return true; + return Constants.HANDLED; } - protected boolean handleTimerStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTimerStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRecordStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRecordStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index bf5bf8bae6fc..790c067c1300 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -316,7 +316,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); @@ -339,52 +340,56 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mDelayedMessageBuffer.removeActiveSource(); return super.handleActiveSource(message); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleInitiateArc(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement initiate arc handler HdmiLogger.debug(TAG + "Stub handleInitiateArc"); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleReportArcInitiate(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportArcInitiate(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement report arc initiate handler HdmiLogger.debug(TAG + "Stub handleReportArcInitiate"); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleReportArcTermination(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportArcTermination(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement report arc terminate handler HdmiLogger.debug(TAG + "Stub handleReportArcTermination"); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleGiveAudioStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED) { reportAudioStatus(message.getSource()); - } else { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.HANDLED; } - return true; + return Constants.ABORT_REFUSED; } @Override @ServiceThreadOnly - protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); // If the audio system is initiating the system audio mode on and TV asks the sam status at // the same time, respond with true. Since we know TV supports sam in this situation. @@ -399,52 +404,53 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mService.sendCecCommand( HdmiCecMessageBuilder.buildReportSystemAudioMode( mAddress, message.getSource(), isSystemAudioModeOnOrTurningOn)); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRequestArcInitiate(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestArcInitiate(HdmiCecMessage message) { assertRunOnServiceThread(); removeAction(ArcInitiationActionFromAvr.class); if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + return Constants.ABORT_UNRECOGNIZED_OPCODE; } else if (!isDirectConnectToTv()) { HdmiLogger.debug("AVR device is not directly connected with TV"); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + return Constants.ABORT_NOT_IN_CORRECT_MODE; } else { addAndStartAction(new ArcInitiationActionFromAvr(this)); + return Constants.HANDLED; } - return true; } @Override @ServiceThreadOnly - protected boolean handleRequestArcTermination(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestArcTermination(HdmiCecMessage message) { assertRunOnServiceThread(); if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + return Constants.ABORT_UNRECOGNIZED_OPCODE; } else if (!isArcEnabled()) { HdmiLogger.debug("ARC is not established between TV and AVR device"); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + return Constants.ABORT_NOT_IN_CORRECT_MODE; } else { removeAction(ArcTerminationActionFromAvr.class); addAndStartAction(new ArcTerminationActionFromAvr(this)); + return Constants.HANDLED; } - return true; } @ServiceThreadOnly - protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) { assertRunOnServiceThread(); HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor"); if (!isSystemAudioControlFeatureEnabled()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } if (!isSystemAudioActivated()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); - return true; + return Constants.ABORT_NOT_IN_CORRECT_MODE; } List<DeviceConfig> config = null; @@ -468,21 +474,20 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } else { AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo(); if (deviceInfo == null) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE); - return true; + return Constants.ABORT_UNABLE_TO_DETERMINE; } sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes); } if (sadBytes.length == 0) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); + return Constants.ABORT_INVALID_OPERAND; } else { mService.sendCecCommand( HdmiCecMessageBuilder.buildReportShortAudioDescriptor( mAddress, message.getSource(), sadBytes)); + return Constants.HANDLED; } - return true; } private byte[] getSupportedShortAudioDescriptors( @@ -624,7 +629,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeRequest(HdmiCecMessage message) { assertRunOnServiceThread(); boolean systemAudioStatusOn = message.getParams().length != 0; // Check if the request comes from a non-TV device. @@ -632,8 +638,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // if non-TV device tries to turn on the feature if (message.getSource() != Constants.ADDR_TV) { if (systemAudioStatusOn) { - handleSystemAudioModeOnFromNonTvDevice(message); - return true; + return handleSystemAudioModeOnFromNonTvDevice(message); } } else { // If TV request the feature on @@ -644,8 +649,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // If TV or Audio System does not support the feature, // will send abort command. if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } mService.sendCecCommand( @@ -660,7 +664,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { if (HdmiUtils.getLocalPortFromPhysicalAddress( sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress()) != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) { - return true; + return Constants.HANDLED; } HdmiDeviceInfo safeDeviceInfoByPath = mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress); @@ -668,29 +672,31 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { switchInputOnReceivingNewActivePath(sourcePhysicalAddress); } } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); if (!checkSupportAndSetSystemAudioMode( HdmiUtils.parseCommandParamSystemAudioStatus(message))) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (!checkSupportAndSetSystemAudioMode( HdmiUtils.parseCommandParamSystemAudioStatus(message))) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; + return Constants.HANDLED; } @ServiceThreadOnly @@ -948,13 +954,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { /** * Handler of System Audio Mode Request on from non TV device */ - void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) { + @Constants.HandleMessageResult + int handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) { if (!isSystemAudioControlFeatureEnabled()) { HdmiLogger.debug( "Cannot turn on" + "system audio mode " + "because the System Audio Control feature is disabled."); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return; + return Constants.ABORT_REFUSED; } // Wake up device mService.wakeUp(); @@ -967,7 +973,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mService.sendCecCommand( HdmiCecMessageBuilder.buildSetSystemAudioMode( mAddress, Constants.ADDR_BROADCAST, true)); - return; + return Constants.HANDLED; } // Check if TV supports System Audio Control. // Handle broadcasting setSystemAudioMode on or aborting message on callback. @@ -983,6 +989,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } } }); + return Constants.HANDLED; } void setTvSystemAudioModeSupport(boolean supported) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 299525207a60..10f6948f8782 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -251,7 +251,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { } @ServiceThreadOnly - protected boolean handleUserControlPressed(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleUserControlPressed(HdmiCecMessage message) { assertRunOnServiceThread(); wakeUpIfActiveSource(); return super.handleUserControlPressed(message); @@ -270,10 +271,11 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { } @ServiceThreadOnly - protected boolean handleSetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); if (!SET_MENU_LANGUAGE) { - return false; + return Constants.ABORT_UNRECOGNIZED_OPCODE; } try { @@ -283,7 +285,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // Do not switch language if the new language is the same as the current one. // This helps avoid accidental country variant switching from en_US to en_AU // due to the limitation of CEC. See the warning below. - return true; + return Constants.HANDLED; } // Don't use Locale.getAvailableLocales() since it returns a locale @@ -298,36 +300,38 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // will always be mapped to en-AU among other variants like en-US, en-GB, // an en-IN, which may not be the expected one. LocalePicker.updateLocale(localeInfo.getLocale()); - return true; + return Constants.HANDLED; } } Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language); - return false; + return Constants.ABORT_INVALID_OPERAND; } catch (UnsupportedEncodingException e) { Slog.w(TAG, "Can't handle <Set Menu Language>", e); - return false; + return Constants.ABORT_INVALID_OPERAND; } } @Override - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { // System Audio Mode only turns on/off when Audio System broadcasts on/off message. // For device with type 4 and 5, it can set system audio mode on/off // when there is another audio system device connected into the system first. if (message.getDestination() != Constants.ADDR_BROADCAST || message.getSource() != Constants.ADDR_AUDIO_SYSTEM || mService.audioSystem() != null) { - return true; + return Constants.HANDLED; } boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message); if (mService.isSystemAudioActivated() != setSystemAudioModeOn) { mService.setSystemAudioActivated(setSystemAudioModeOn); } - return true; + return Constants.HANDLED; } @Override - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { // Only directly addressed System Audio Mode Status message can change internal // system audio mode status. if (message.getDestination() == mAddress @@ -337,25 +341,27 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { mService.setSystemAudioActivated(setSystemAudioModeOn); } } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2); handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingInformation(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingInformation(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 2ed84811250e..979a1d4b0718 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -203,7 +203,8 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { } @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); @@ -215,20 +216,22 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { if (isRoutingControlFeatureEnabled()) { switchInputOnReceivingNewActivePath(physicalAddress); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); maySendActiveSource(message.getSource()); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSetStreamPath(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetStreamPath(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); // If current device is the target path, set to Active Source. @@ -242,12 +245,13 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleSetStreamPath()"); } switchInputOnReceivingNewActivePath(physicalAddress); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2); if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) { @@ -256,16 +260,16 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingChange()"); } if (!isRoutingControlFeatureEnabled()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingInformation(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingInformation(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) { @@ -274,11 +278,10 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingInformation()"); } if (!isRoutingControlFeatureEnabled()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } // Method to switch Input with the new Active Path. diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 90d64339eac0..cd66a8fc7f01 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -47,6 +47,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; @@ -210,11 +211,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - boolean dispatchMessage(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int dispatchMessage(HdmiCecMessage message) { assertRunOnServiceThread(); if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived() && mStandbyHandler.handleCommand(message)) { - return true; + return Constants.HANDLED; } return super.onMessage(message); } @@ -409,7 +412,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); @@ -429,21 +433,22 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); mDelayedMessageBuffer.add(message); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleInactiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleInactiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #10 // Ignore <Inactive Source> from non-active source device. if (getActiveSource().logicalAddress != message.getSource()) { - return true; + return Constants.HANDLED; } if (isProhibitMode()) { - return true; + return Constants.HANDLED; } int portId = getPrevPortId(); if (portId != Constants.INVALID_PORT_ID) { @@ -452,10 +457,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo( message.getSource()); if (inactiveSource == null) { - return true; + return Constants.HANDLED; } if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) { - return true; + return Constants.HANDLED; } // TODO: Switch the TV freeze mode off @@ -468,29 +473,31 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #19 if (mAddress == getActiveSource().logicalAddress) { mService.sendCecCommand( HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath())); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleGetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); if (!broadcastMenuLanguage(mService.getLanguage())) { Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); } - return true; + return Constants.HANDLED; } @ServiceThreadOnly @@ -506,7 +513,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportPhysicalAddress(HdmiCecMessage message) { super.handleReportPhysicalAddress(message); int path = HdmiUtils.twoBytesToInt(message.getParams()); int address = message.getSource(); @@ -516,19 +524,21 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { handleNewDeviceAtTheTailOfActivePath(path); } startNewDeviceAction(ActiveSource.of(address, path), type); - return true; + return Constants.HANDLED; } @Override - protected boolean handleTimerStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTimerStatus(HdmiCecMessage message) { // Do nothing. - return true; + return Constants.HANDLED; } @Override - protected boolean handleRecordStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRecordStatus(HdmiCecMessage message) { // Do nothing. - return true; + return Constants.HANDLED; } void startNewDeviceAction(ActiveSource activeSource, int deviceType) { @@ -590,7 +600,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #21 byte[] params = message.getParams(); @@ -601,27 +612,29 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { int newPath = HdmiUtils.twoBytesToInt(params, 2); addAndStartAction(new RoutingControlAction(this, newPath, true, null)); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleReportAudioStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (mService.getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_DISABLED) { - return false; + return Constants.ABORT_REFUSED; } boolean mute = HdmiUtils.isAudioStatusMute(message); int volume = HdmiUtils.getAudioStatusVolume(message); setAudioStatus(mute, volume); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleTextViewOn(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTextViewOn(HdmiCecMessage message) { assertRunOnServiceThread(); // Note that <Text View On> (and <Image View On>) command won't be handled here in @@ -634,12 +647,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (mService.isPowerStandbyOrTransient() && getAutoWakeup()) { mService.wakeUp(); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleImageViewOn(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleImageViewOn(HdmiCecMessage message) { assertRunOnServiceThread(); // Currently, it's the same as <Text View On>. return handleTextViewOn(message); @@ -977,7 +991,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleInitiateArc(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); if (!canStartArcUpdateAction(message.getSource(), true)) { @@ -985,13 +1000,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (avrDeviceInfo == null) { // AVR may not have been discovered yet. Delay the message processing. mDelayedMessageBuffer.add(message); - return true; + return Constants.HANDLED; } - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) { displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); } - return true; + return Constants.ABORT_REFUSED; } // In case where <Initiate Arc> is started by <Request ARC Initiation> @@ -1000,7 +1014,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, message.getSource(), true); addAndStartAction(action); - return true; + return Constants.HANDLED; } private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) { @@ -1022,11 +1036,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleTerminateArc(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTerminateArc(HdmiCecMessage message) { assertRunOnServiceThread(); if (mService .isPowerStandbyOrTransient()) { setArcStatus(false); - return true; + return Constants.HANDLED; } // Do not check ARC configuration since the AVR might have been already removed. // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by @@ -1035,12 +1050,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, message.getSource(), false); addAndStartAction(action); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message); if (!isMessageForSystemAudio(message)) { @@ -1049,30 +1065,29 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mDelayedMessageBuffer.add(message); } else { HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) { HdmiLogger.debug("Ignoring <Set System Audio Mode> message " + "because the System Audio Control feature is disabled: %s", message); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } removeAction(SystemAudioAutoInitiationAction.class); SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, message.getSource(), systemAudioStatus, null); addAndStartAction(action); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (!isMessageForSystemAudio(message)) { HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message); // Ignore this message. - return true; + return Constants.HANDLED; } boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled(); boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message); @@ -1089,13 +1104,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { setSystemAudioMode(tvSystemAudioMode); } - return true; + return Constants.HANDLED; } // Seq #53 @Override @ServiceThreadOnly - protected boolean handleRecordTvScreen(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRecordTvScreen(HdmiCecMessage message) { List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); if (!actions.isEmpty()) { // Assumes only one OneTouchRecordAction. @@ -1107,25 +1123,21 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } // The default behavior of <Record TV Screen> is replying <Feature Abort> with // "Cannot provide source". - mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE); - return true; + return Constants.ABORT_CANNOT_PROVIDE_SOURCE; } int recorderAddress = message.getSource(); byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); - int reason = startOneTouchRecord(recorderAddress, recordSource); - if (reason != Constants.ABORT_NO_ERROR) { - mService.maySendFeatureAbortCommand(message, reason); - } - return true; + return startOneTouchRecord(recorderAddress, recordSource); } @Override - protected boolean handleTimerClearedStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTimerClearedStatus(HdmiCecMessage message) { byte[] params = message.getParams(); int timerClearedStatusData = params[0] & 0xFF; announceTimerRecordingResult(message.getSource(), timerClearedStatusData); - return true; + return Constants.HANDLED; } void announceOneTouchRecordResult(int recorderAddress, int result) { @@ -1337,6 +1349,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Seq #54 and #55 @ServiceThreadOnly + @Constants.HandleMessageResult int startOneTouchRecord(int recorderAddress, byte[] recordSource) { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { @@ -1362,7 +1375,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" + Arrays.toString(recordSource)); - return Constants.ABORT_NO_ERROR; + return Constants.HANDLED; } @ServiceThreadOnly @@ -1494,9 +1507,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - protected boolean handleMenuStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleMenuStatus(HdmiCecMessage message) { // Do nothing and just return true not to prevent from responding <Feature Abort>. - return true; + return Constants.HANDLED; } @Constants.RcProfile diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 03a83380246f..031c057018ad 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -372,8 +372,7 @@ public class HdmiControlService extends SystemService { private HdmiCecMessageValidator mMessageValidator; - private final HdmiCecPowerStatusController mPowerStatusController = - new HdmiCecPowerStatusController(this); + private HdmiCecPowerStatusController mPowerStatusController; @ServiceThreadOnly private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault()); @@ -427,7 +426,7 @@ public class HdmiControlService extends SystemService { // Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing. private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter(); - private CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer(this); + private CecMessageBuffer mCecMessageBuffer; private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer(); @@ -493,6 +492,9 @@ public class HdmiControlService extends SystemService { mIoLooper = mIoThread.getLooper(); } + if (mPowerStatusController == null) { + mPowerStatusController = new HdmiCecPowerStatusController(this); + } mPowerStatusController.setPowerStatus(getInitialPowerStatus()); mProhibitMode = false; mHdmiControlEnabled = mHdmiCecConfig.getIntValue( @@ -501,6 +503,9 @@ public class HdmiControlService extends SystemService { HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)); mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true); + if (mCecMessageBuffer == null) { + mCecMessageBuffer = new CecMessageBuffer(this); + } if (mCecController == null) { mCecController = HdmiCecController.create(this, getAtomWriter()); } @@ -948,11 +953,10 @@ public class HdmiControlService extends SystemService { /** * Returns {@link Looper} for IO operation. - * - * <p>Declared as package-private. */ @Nullable - Looper getIoLooper() { + @VisibleForTesting + protected Looper getIoLooper() { return mIoLooper; } @@ -974,10 +978,9 @@ public class HdmiControlService extends SystemService { /** * Returns {@link Looper} of main thread. Use this {@link Looper} instance * for tasks that are running on main service thread. - * - * <p>Declared as package-private. */ - Looper getServiceLooper() { + @VisibleForTesting + protected Looper getServiceLooper() { return mHandler.getLooper(); } @@ -1015,8 +1018,9 @@ public class HdmiControlService extends SystemService { /** * Returns version of CEC. */ + @VisibleForTesting @HdmiControlManager.HdmiCecVersion - int getCecVersion() { + protected int getCecVersion() { return mCecVersion; } @@ -1087,23 +1091,30 @@ public class HdmiControlService extends SystemService { } @ServiceThreadOnly - boolean handleCecCommand(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int handleCecCommand(HdmiCecMessage message) { assertRunOnServiceThread(); int errorCode = mMessageValidator.isValid(message); if (errorCode != HdmiCecMessageValidator.OK) { // We'll not response on the messages with the invalid source or destination // or with parameter length shorter than specified in the standard. if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) { - maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); + return Constants.ABORT_INVALID_OPERAND; } - return true; + return Constants.HANDLED; } getHdmiCecNetwork().handleCecMessage(message); - if (dispatchMessageToLocalDevice(message)) { - return true; + + @Constants.HandleMessageResult int handleMessageResult = + dispatchMessageToLocalDevice(message); + if (handleMessageResult == Constants.NOT_HANDLED + && !mAddressAllocated + && mCecMessageBuffer.bufferMessage(message)) { + return Constants.HANDLED; } - return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false; + return handleMessageResult; } void enableAudioReturnChannel(int portId, boolean enabled) { @@ -1111,19 +1122,25 @@ public class HdmiControlService extends SystemService { } @ServiceThreadOnly - private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int dispatchMessageToLocalDevice(HdmiCecMessage message) { assertRunOnServiceThread(); for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { - if (device.dispatchMessage(message) + @Constants.HandleMessageResult int messageResult = device.dispatchMessage(message); + if (messageResult != Constants.NOT_HANDLED && message.getDestination() != Constants.ADDR_BROADCAST) { - return true; + return messageResult; } } - if (message.getDestination() != Constants.ADDR_BROADCAST) { + // We should never respond <Feature Abort> to a broadcast message + if (message.getDestination() == Constants.ADDR_BROADCAST) { + return Constants.HANDLED; + } else { HdmiLogger.warning("Unhandled cec command:" + message); + return Constants.NOT_HANDLED; } - return false; } /** @@ -2970,7 +2987,7 @@ public class HdmiControlService extends SystemService { } @VisibleForTesting - boolean isStandbyMessageReceived() { + protected boolean isStandbyMessageReceived() { return mStandbyMessageReceived; } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 092502709f34..0f137418a89c 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.ShellCallback; 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 @@ public class InputManagerService extends IInputManager.Stub } 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 @@ public class InputManagerService extends IInputManager.Stub } 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(); + } else { + 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(); + throw new ArrayIndexOutOfBoundsException("Repeat index " + mRepeat + + " must be within the bounds of the pattern.length " + + mPattern.length); } - } else { - Slog.w(TAG, "Pre-baked and composed effects aren't supported on input devices"); } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d17c24c25b78..c9364c62d8b7 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 @@ package com.android.server.inputmethod; 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.StartInputReason; 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.InputMethodSubtypeSwitchingController.ImeS 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { mShowRequested = true; - if (mAccessibilityRequestingNoSoftKeyboard) { + if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) { return false; } @@ -4101,7 +4101,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ @BinderThread @Override - @GuardedBy("mMethodMap") public void startProtoDump(byte[] protoDump, int source, String where, IVoidResultCallback resultCallback) { CallbackUtils.onResult(resultCallback, () -> { @@ -4198,7 +4197,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub }); } - @GuardedBy("mMethodMap") private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (mMethodMap) { final long token = proto.start(fieldId); @@ -5227,26 +5225,71 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + 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 */); + } + } - 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); - }); + /** + * {@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(); - return; } + }; + @BinderThread + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + + PriorityDump.dump(mPriorityDumper, fd, pw, args); + } + + @BinderThread + private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args, + boolean isCritical) { IInputMethod method; ClientState client; ClientState focusedWindowClient; @@ -5310,6 +5353,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** - * 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 c93c4b1f21b7..dc3596b6c2a7 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.location.IGeocodeProvider; 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 @@ public class GeocoderProxy { 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 class GeocoderProxy { */ 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 @@ public class GeocoderProxy { 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 e1c87000ea89..6ac6e77342be 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.IBinder; 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 @@ public class HardwareActivityRecognitionProxy { 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 @@ public class HardwareActivityRecognitionProxy { 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 @@ public class HardwareActivityRecognitionProxy { 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 2920ddb2d76d..864aa33a58d0 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 android.location.provider.LocationProviderBase.ACTION_NETWORK_PROV 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 class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { private final Context mContext; private final Injector mInjector; - private final LocationEventLog mEventLog; private final LocalService mLocalService; private final GeofenceManager mGeofenceManager; @@ -261,18 +260,20 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { } LocationProviderManager manager = new LocationProviderManager(mContext, mInjector, - mEventLog, providerName, mPassiveManager); + providerName, mPassiveManager); addLocationProviderManager(manager, null); return manager; } @@ -335,7 +336,7 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { } 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 @@ public class LocationManagerService extends ILocationManager.Stub { // 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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { mGnssManagerService.onSystemReady(); LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector, - mEventLog, GPS_PROVIDER, mPassiveManager); + GPS_PROVIDER, mPassiveManager); addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider()); } @@ -476,7 +476,7 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { ipw.println("Event Log:"); ipw.increaseIndent(); - mEventLog.iterate(ipw::println); + EVENT_LOG.iterate(ipw::println); ipw.decreaseIndent(); } @@ -1456,7 +1456,7 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 29ce3783c4a1..045e06d001e6 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 @@ import com.android.internal.util.Preconditions; /** 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 @@ public class LocationEventLog extends LocalEventLog { } } - 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 @@ public class LocationEventLog extends LocalEventLog { 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 @@ public class LocationEventLog extends LocalEventLog { @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 @@ public class LocationEventLog extends LocalEventLog { } } + 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 @@ public class LocationEventLog extends LocalEventLog { @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 c70714932792..90b446ecb15d 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.RemoteException; 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 @@ public final class GeofenceProxy { 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 @@ public final class GeofenceProxy { 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 cc00d5684991..53407d928012 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 android.os.PowerManager.locationPowerSaveModeToString; 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 @@ public abstract class LocationPowerSaveModeHelper { 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 @@ public abstract class LocationPowerSaveModeHelper { 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 c47a64d6d9a7..a675f5467b3b 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 android.os.PowerSaveState; 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 @@ public class SystemLocationPowerSaveModeHelper extends LocationPowerSaveModeHelp @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 3f7345e7c0c3..632ed6ef192a 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.database.ContentObserver; 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 @@ public class SystemSettingsHelper extends SettingsHelper { */ @Override public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) { - int userId = ActivityManager.getCurrentUser(); - - 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); + int[] userIds; + try { + userIds = ActivityManager.getService().getRunningUserIds(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + 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(); + + 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; + } - List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser( - userId); - if (!locationPackageWhitelist.isEmpty()) { - ipw.println("Location Allow Packages:"); + ipw.print("user "); + ipw.print(userId); + ipw.println(":"); ipw.increaseIndent(); + + 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 d4a8fbd0ceb0..ed1e65457b24 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 @@ public class SystemUserInfoHelper extends UserInfoHelper { */ @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 0fcc1ecc4c1a..c835370a6f86 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 @@ package com.android.server.location.injector; 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 @@ public abstract class UserInfoHelper { 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 dc8b1d001c74..102263b5f3c2 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.LocationManagerService.TAG; 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.FgThread; 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 @@ public class LocationProviderManager extends + 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 @@ public class LocationProviderManager extends onProviderListenerRegister(); if (mForeground) { - mEventLog.logProviderClientForeground(mName, getIdentity()); + EVENT_LOG.logProviderClientForeground(mName, getIdentity()); } } @@ -358,7 +358,7 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends onProviderListenerInactive(); - mEventLog.logProviderClientInactive(mName, getIdentity()); + EVENT_LOG.logProviderClientInactive(mName, getIdentity()); } /** @@ -543,9 +543,9 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends listener.deliverOnLocationChanged(deliverLocationResult, mUseWakeLock ? mWakeLock::release : null); - mEventLog.logProviderDeliveredLocations(mName, locationResult.size(), + EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(), getIdentity()); } @@ -1178,7 +1178,7 @@ public class LocationProviderManager extends // 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 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends @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 @@ public class LocationProviderManager extends mEnabledListeners = new ArrayList<>(); mProviderRequestListeners = new CopyOnWriteArrayList<>(); - mEventLog = eventLog; mLocationManagerInternal = Objects.requireNonNull( LocalServices.getService(LocationManagerInternal.class)); mSettingsHelper = injector.getSettingsHelper(); @@ -1477,7 +1475,7 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends } @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 @@ public class LocationProviderManager extends } // 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 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends 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 @@ public class LocationProviderManager extends } } - private @Nullable LocationResult getPermittedLocationResult( + @Nullable LocationResult getPermittedLocationResult( @Nullable LocationResult fineLocationResult, @PermissionLevel int permissionLevel) { switch (permissionLevel) { case PERMISSION_FINE: @@ -2538,6 +2536,8 @@ public class LocationProviderManager extends 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 027f4e94f55b..b35af4f6475c 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.location.provider.ProviderRequest; 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 @@ import java.util.Collection; */ 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 6f4aa642500f..ab7e526a8e68 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 android.location.provider.ProviderRequest.INTERVAL_DISABLED; 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.annotations.GuardedBy; 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 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation 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 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation @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 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation 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 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation } } 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 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation } 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 c86e49bc7948..5df78704d002 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 @@ package com.android.server.location.provider.proxy; 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.ILocationProviderManager; 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 @@ public class ProxyLocationProvider extends AbstractLocationProvider { final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0); @GuardedBy("mLock") - Proxy mProxy; + @Nullable Runnable mResetter; + @GuardedBy("mLock") + @Nullable Proxy mProxy; @GuardedBy("mLock") - @Nullable ComponentName mService; + @Nullable BoundServiceInfo mBoundServiceInfo; private volatile ProviderRequest mRequest; - private ProxyLocationProvider(Context context, String action, int enableOverlayResId, - int nonOverlayPackageResId) { + private ProxyLocationProvider(Context context, String provider, String action, + int enableOverlayResId, int nonOverlayPackageResId) { // safe to use direct executor since our locks are not acquired in a code path invoked by // our owning provider super(DIRECT_EXECUTOR, null, null, Collections.emptySet()); mContext = context; - mServiceWatcher = new ServiceWatcher(context, action, this::onBind, - this::onUnbind, enableOverlayResId, nonOverlayPackageResId); + mServiceWatcher = ServiceWatcher.create(context, provider, + new CurrentUserServiceSupplier(context, action, enableOverlayResId, + nonOverlayPackageResId), this); mProxy = null; mRequest = ProviderRequest.EMPTY_REQUEST; @@ -104,21 +110,13 @@ public class ProxyLocationProvider extends AbstractLocationProvider { 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 @@ public class ProxyLocationProvider extends AbstractLocationProvider { } } - 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 @@ public class ProxyLocationProvider extends AbstractLocationProvider { @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 @@ public class ProxyLocationProvider extends AbstractLocationProvider { @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 @@ public class ProxyLocationProvider extends AbstractLocationProvider { // executed on binder thread @Override public void onInitialize(boolean allowed, ProviderProperties properties, - @Nullable String packageName, @Nullable String attributionTag) { + @Nullable String attributionTag) { synchronized (mLock) { if (mProxy != this) { return; } - CallerIdentity identity; - if (packageName == null) { - packageName = guessPackageName(mContext, Binder.getCallingUid(), - Objects.requireNonNull(mService).getPackageName()); - // unsafe is ok since the package is coming direct from the package manager here - identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - } else { - identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); + if (mResetter != null) { + FgThread.getHandler().removeCallbacks(mResetter); + mResetter = null; + } + + // set extra attribution tags from manifest if necessary + String[] attributionTags = new String[0]; + if (mBoundServiceInfo.getMetadata() != null) { + String tagsStr = mBoundServiceInfo.getMetadata().getString(EXTRA_LOCATION_TAGS); + if (!TextUtils.isEmpty(tagsStr)) { + attributionTags = tagsStr.split(LOCATION_TAGS_SEPARATOR); + } } + ArraySet<String> extraAttributionTags = new ArraySet<>(attributionTags); + + // unsafe is ok since we trust the package name already + CallerIdentity identity = CallerIdentity.fromBinderUnsafe( + mBoundServiceInfo.getComponentName().getPackageName(), + attributionTag); - setState(prevState -> prevState + setState(prevState -> State.EMPTY_STATE .withAllowed(allowed) .withProperties(properties) - .withIdentity(identity)); + .withIdentity(identity) + .withExtraAttributionTags(extraAttributionTags)); } } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 6e99cba6ea91..76ecc1acc7ac 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -15,14 +15,17 @@ */ package com.android.server.locksettings; + import static android.os.UserHandle.USER_SYSTEM; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.UserInfo; import android.os.Handler; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.Settings; @@ -35,6 +38,8 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.widget.RebootEscrowListener; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -65,6 +70,22 @@ class RebootEscrowManager { public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count"; static final String REBOOT_ESCROW_KEY_ARMED_TIMESTAMP = "reboot_escrow_key_stored_timestamp"; + static final String REBOOT_ESCROW_KEY_PROVIDER = "reboot_escrow_key_provider"; + + /** + * The verified boot 2.0 vbmeta digest of the current slot, the property value is always + * available after boot. + */ + static final String VBMETA_DIGEST_PROP_NAME = "ro.boot.vbmeta.digest"; + /** + * The system prop contains vbmeta digest of the inactive slot. The build property is set after + * an OTA update. RebootEscrowManager will store it in disk before the OTA reboot, so the value + * is available for vbmeta digest verification after the device reboots. + */ + static final String OTHER_VBMETA_DIGEST_PROP_NAME = "ota.other.vbmeta_digest"; + static final String REBOOT_ESCROW_KEY_VBMETA_DIGEST = "reboot_escrow_key_vbmeta_digest"; + static final String REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST = + "reboot_escrow_key_other_vbmeta_digest"; /** * Number of boots until we consider the escrow data to be stale for the purposes of metrics. @@ -86,6 +107,31 @@ class RebootEscrowManager { private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3; private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30; + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_NONE, + ERROR_UNKNOWN, + ERROR_NO_PROVIDER, + ERROR_LOAD_ESCROW_KEY, + ERROR_RETRY_COUNT_EXHAUSTED, + ERROR_UNLOCK_ALL_USERS, + ERROR_PROVIDER_MISMATCH, + ERROR_KEYSTORE_FAILURE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RebootEscrowErrorCode { + } + + static final int ERROR_NONE = 0; + static final int ERROR_UNKNOWN = 1; + static final int ERROR_NO_PROVIDER = 2; + static final int ERROR_LOAD_ESCROW_KEY = 3; + static final int ERROR_RETRY_COUNT_EXHAUSTED = 4; + static final int ERROR_UNLOCK_ALL_USERS = 5; + static final int ERROR_PROVIDER_MISMATCH = 6; + static final int ERROR_KEYSTORE_FAILURE = 7; + + private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE; + /** * Logs events for later debugging in bugreports. */ @@ -199,6 +245,10 @@ class RebootEscrowManager { 0); } + public long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + public int getLoadEscrowDataRetryLimit() { return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); @@ -221,6 +271,11 @@ class RebootEscrowManager { public RebootEscrowEventLog getEventLog() { return new RebootEscrowEventLog(); } + + public String getVbmetaDigest(boolean other) { + return other ? SystemProperties.get(OTHER_VBMETA_DIGEST_PROP_NAME) + : SystemProperties.get(VBMETA_DIGEST_PROP_NAME); + } } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { @@ -261,6 +316,7 @@ class RebootEscrowManager { if (rebootEscrowUsers.isEmpty()) { Slog.i(TAG, "No reboot escrow data found for users," + " skipping loading escrow data"); + clearMetricsStorage(); return; } @@ -284,6 +340,7 @@ class RebootEscrowManager { } Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts"); + mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED; onGetRebootEscrowKeyFailed(users, attemptNumber); } @@ -307,6 +364,17 @@ class RebootEscrowManager { } if (escrowKey == null) { + if (mLoadEscrowDataErrorCode == ERROR_NONE) { + // Specifically check if the RoR provider has changed after reboot. + int providerType = mInjector.serverBasedResumeOnReboot() + ? RebootEscrowProviderInterface.TYPE_SERVER_BASED + : RebootEscrowProviderInterface.TYPE_HAL; + if (providerType != mStorage.getInt(REBOOT_ESCROW_KEY_PROVIDER, -1, USER_SYSTEM)) { + mLoadEscrowDataErrorCode = ERROR_PROVIDER_MISMATCH; + } else { + mLoadEscrowDataErrorCode = ERROR_LOAD_ESCROW_KEY; + } + } onGetRebootEscrowKeyFailed(users, attemptNumber + 1); return; } @@ -321,9 +389,49 @@ class RebootEscrowManager { // Clear the old key in keystore. A new key will be generated by new RoR requests. mKeyStoreManager.clearKeyStoreEncryptionKey(); + if (!allUsersUnlocked && mLoadEscrowDataErrorCode == ERROR_NONE) { + mLoadEscrowDataErrorCode = ERROR_UNLOCK_ALL_USERS; + } onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1); } + private void clearMetricsStorage() { + mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_VBMETA_DIGEST, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_PROVIDER, USER_SYSTEM); + } + + private int getVbmetaDigestStatusOnRestoreComplete() { + String currentVbmetaDigest = mInjector.getVbmetaDigest(false); + String vbmetaDigestStored = mStorage.getString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, + "", USER_SYSTEM); + String vbmetaDigestOtherStored = mStorage.getString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, + "", USER_SYSTEM); + + // The other vbmeta digest is never set, assume no slot switch is attempted. + if (vbmetaDigestOtherStored.isEmpty()) { + if (currentVbmetaDigest.equals(vbmetaDigestStored)) { + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; + } + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH; + } + + // The other vbmeta digest is set, we expect to boot into the new slot. + if (currentVbmetaDigest.equals(vbmetaDigestOtherStored)) { + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; + } else if (currentVbmetaDigest.equals(vbmetaDigestStored)) { + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_FALLBACK_SLOT; + } + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH; + } + private void reportMetricOnRestoreComplete(boolean success, int attemptCount) { int serviceType = mInjector.serverBasedResumeOnReboot() ? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED @@ -331,26 +439,32 @@ class RebootEscrowManager { long armedTimestamp = mStorage.getLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, -1, USER_SYSTEM); - mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM); - int escrowDurationInSeconds = armedTimestamp != -1 - ? (int) (System.currentTimeMillis() - armedTimestamp) / 1000 : -1; + int escrowDurationInSeconds = -1; + long currentTimeStamp = mInjector.getCurrentTimeMillis(); + if (armedTimestamp != -1 && currentTimeStamp > armedTimestamp) { + escrowDurationInSeconds = (int) (currentTimeStamp - armedTimestamp) / 1000; + } - // TODO(b/179105110) design error code; and report the true value for other fields. - int vbmetaDigestStatus = FrameworkStatsLog - .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; + int vbmetaDigestStatus = getVbmetaDigestStatusOnRestoreComplete(); + if (!success && mLoadEscrowDataErrorCode == ERROR_NONE) { + mLoadEscrowDataErrorCode = ERROR_UNKNOWN; + } - mInjector.reportMetric(success, 0 /* error code */, serviceType, attemptCount, + // TODO(179105110) report the duration since boot complete. + mInjector.reportMetric(success, mLoadEscrowDataErrorCode, serviceType, attemptCount, escrowDurationInSeconds, vbmetaDigestStatus, -1); + + mLoadEscrowDataErrorCode = ERROR_NONE; } private void onEscrowRestoreComplete(boolean success, int attemptCount) { int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM); - mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); int bootCountDelta = mInjector.getBootCount() - previousBootCount; if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) { reportMetricOnRestoreComplete(success, attemptCount); } + clearMetricsStorage(); } private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException { @@ -358,6 +472,14 @@ class RebootEscrowManager { if (rebootEscrowProvider == null) { Slog.w(TAG, "Had reboot escrow data for users, but RebootEscrowProvider is unavailable"); + mLoadEscrowDataErrorCode = ERROR_NO_PROVIDER; + return null; + } + + // Server based RoR always need the decryption key from keystore. + if (rebootEscrowProvider.getType() == RebootEscrowProviderInterface.TYPE_SERVER_BASED + && kk == null) { + mLoadEscrowDataErrorCode = ERROR_KEYSTORE_FAILURE; return null; } @@ -463,7 +585,7 @@ class RebootEscrowManager { return; } - mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); + clearMetricsStorage(); rebootEscrowProvider.clearRebootEscrowKey(); List<UserInfo> users = mUserManager.getUsers(); @@ -486,6 +608,9 @@ class RebootEscrowManager { return false; } + int actualProviderType = rebootEscrowProvider.getType(); + // TODO(b/183140900) Fail the reboot if provider type mismatches. + RebootEscrowKey escrowKey; synchronized (mKeyGenerationLock) { escrowKey = mPendingRebootEscrowKey; @@ -505,8 +630,14 @@ class RebootEscrowManager { boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk); if (armedRebootEscrow) { mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); - mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, System.currentTimeMillis(), + mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(), + USER_SYSTEM); + // Store the vbmeta digest of both slots. + mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false), USER_SYSTEM); + mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, + mInjector.getVbmetaDigest(true), USER_SYSTEM); + mStorage.setInt(REBOOT_ESCROW_KEY_PROVIDER, actualProviderType, USER_SYSTEM); mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS); } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java index 4b00772088f2..e8f6f4abd030 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java @@ -60,6 +60,11 @@ class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface { } @Override + public int getType() { + return TYPE_HAL; + } + + @Override public boolean hasRebootEscrowSupport() { return mInjector.getRebootEscrow() != null; } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java index af6faad3c76e..e106d817c533 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java @@ -16,7 +16,11 @@ package com.android.server.locksettings; +import android.annotation.IntDef; + import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import javax.crypto.SecretKey; @@ -28,6 +32,21 @@ import javax.crypto.SecretKey; * @hide */ public interface RebootEscrowProviderInterface { + @IntDef(prefix = {"TYPE_"}, value = { + TYPE_HAL, + TYPE_SERVER_BASED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RebootEscrowProviderType { + } + int TYPE_HAL = 0; + int TYPE_SERVER_BASED = 1; + + /** + * Returns the reboot escrow provider type. + */ + @RebootEscrowProviderType int getType(); + /** * Returns true if the secure store/discard of reboot escrow key is supported. */ diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java index 697bf08a232e..28669875f1cd 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -95,6 +95,11 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } @Override + public int getType() { + return TYPE_SERVER_BASED; + } + + @Override public boolean hasRebootEscrowSupport() { return mInjector.getServiceConnection() != null; } diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java index cc3a002adc84..df1eb6d9fe3c 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.IpAssignment; 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.IOException; 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 @@ public class IpConfigStore { 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 @@ public class IpConfigStore { 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 @@ public class IpConfigStore { 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 @@ public class IpConfigStore { 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 @@ public class IpConfigStore { 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 @@ public class IpConfigStore { } 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 @@ public class IpConfigStore { 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 @@ public class IpConfigStore { 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 @@ public class IpConfigStore { } } 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 @@ public class IpConfigStore { 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 302e6572a245..8c3c42374acb 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.Nullable; 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.content.res.Resources; 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 android.util.Xml; 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.FileNotFoundException; 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } public int getShortcutCount() { - return mShortcuts.size(); + final int[] count = new int[1]; + forEachShortcut(si -> count[0]++); + return count[0]; } @Override @@ -221,17 +236,20 @@ class ShortcutPackage extends ShortcutPackageItem { // - 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); - - shortcut.setDisabledReason(restoreBlockReason); - if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { - shortcut.addFlags(ShortcutInfo.FLAG_DISABLED); - } - }); - } + forEachShortcutMutateIf(si -> { + if (!si.hasFlags(ShortcutInfo.FLAG_SHADOW) + && si.getDisabledReason() == restoreBlockReason + && restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { + return false; + } + si.clearFlags(ShortcutInfo.FLAG_SHADOW); + + 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 @@ class ShortcutPackage extends ShortcutPackageItem { */ @Nullable public ShortcutInfo findShortcutById(String id) { - return mShortcuts.get(id); + return getShortcutById(id); } public boolean isShortcutExistsAndInvisibleToPublisher(String id) { @@ -265,7 +283,7 @@ class ShortcutPackage extends ShortcutPackageItem { } 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 @@ class ShortcutPackage extends ShortcutPackageItem { * 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } 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 @@ class ShortcutPackage extends ShortcutPackageItem { * @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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { * @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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { si.setActivity(null); } }); - return null; } else { forceDeleteShortcutInner(shortcutId); @@ -596,7 +616,7 @@ class ShortcutPackage extends ShortcutPackageItem { } 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 @@ class ShortcutPackage extends ShortcutPackageItem { * <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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { // 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 @@ class ShortcutPackage extends ShortcutPackageItem { if (!getPinnedByAnyLauncher) { if (si.isFloating() && !si.isCached()) { if (!isPinnedByCaller) { - continue; + return; } } } @@ -804,7 +802,7 @@ class ShortcutPackage extends ShortcutPackageItem { } result.add(clone); } - } + }); } public void resetThrottling() { @@ -874,7 +872,7 @@ class ShortcutPackage extends ShortcutPackageItem { * the app's Xml resource. */ int getSharingShortcutCount() { - if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) { + if (getShortcutCount() == 0 || mShareTargets.isEmpty()) { return 0; } @@ -912,14 +910,12 @@ class ShortcutPackage extends ShortcutPackageItem { * 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 @@ class ShortcutPackage extends ShortcutPackageItem { * @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 @@ class ShortcutPackage extends ShortcutPackageItem { // 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 (!si.hasAnyResources() || publisherRes == null) { + return false; + } - // 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.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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { // 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 @@ class ShortcutPackage extends ShortcutPackageItem { } // 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 @@ class ShortcutPackage extends ShortcutPackageItem { /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false, ShortcutInfo.DISABLED_REASON_APP_CHANGED); } - removeOrphans(); } + removeOrphans(); + adjustRanks(); return changed; } @@ -1279,25 +1262,21 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { // (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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { * 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; - - 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()); - }); + final Resources publisherRes = getPackageResources(); + final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); - 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 @@ class ShortcutPackage extends ShortcutPackageItem { /** 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } // 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 @@ class ShortcutPackage extends ShortcutPackageItem { /** @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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { | (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 ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts; - final int size = shortcuts.size(); - for (int i = 0; i < size; i++) { - final ShortcutInfo si = shortcuts.valueAt(i); + 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]; - 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 @@ class ShortcutPackage extends ShortcutPackageItem { @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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { @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 @@ class ShortcutPackage extends ShortcutPackageItem { public void verifyStates() { super.verifyStates(); - boolean failed = false; + final boolean[] failed = new boolean[1]; final ShortcutService s = mShortcutUser.mService; @@ -2098,7 +2057,7 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } // 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } else { mPackageIdentifiers.remove(packageName); } - resetAppSearch(null); + resetAppSearch(session -> AndroidFuture.completedFuture(true)); } void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut, @@ -2212,160 +2170,295 @@ class ShortcutPackage extends ShortcutPackageItem { 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; + }); } - /** - * Merge/replace shortcuts parsed from xml file. - */ - void restoreParsedShortcuts(final boolean replace) { + 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 boolean verifyRanksSequential(List<ShortcutInfo> list) { - boolean failed = false; + 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; + }); + } - for (int i = 0; i < list.size(); i++) { - final ShortcutInfo si = list.get(i); - if (si.getRank() != i) { - failed = true; - Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() - + " rank=" + si.getRank() + " but expected to be "+ i); + 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); } - return failed; } - private void runInAppSearch( - Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) { - if (mShortcutUser == null) { - Slog.w(TAG, "shortcut user is null"); - return; + 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) { - 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); + 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); } - upstream.next(); - } finally { - Binder.restoreCallingIdentity(callingIdentity); + } else { + return resetAppSearch(cb); } - ConcurrentUtils.waitForCountDownNoInterrupt(latch, 500, - "timeout accessing shortcut"); - } else { - resetAppSearch(observers); + } finally { + StrictMode.setThreadPolicy(oldPolicy); } } } - private void resetAppSearch( - Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) { - final CountDownLatch latch = new CountDownLatch(1); + 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(); - mShortcutUser.runInAppSearch(searchContext, result -> { + 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()) { - Slog.e(TAG, "error getting search session during lazy init, " - + result.getErrorMessage()); - latch.countDown(); + future.completeExceptionally( + new IllegalArgumentException(result.getErrorMessage())); 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(); + future.complete(session); }); - 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(); - }); - }; + return future; } /** - * TODO: Replace this temporary implementation with proper async framework + * Merge/replace shortcuts parsed from xml file. */ - 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 restoreParsedShortcuts(final boolean replace) { + if (replace) { + removeShortcuts(); } + saveShortcut(mShortcuts); + } - void next() { - if (mObservers.isEmpty()) { - mLatch.countDown(); - return; - } - mObservers.remove(0).accept(mSession); - } + private boolean verifyRanksSequential(List<ShortcutInfo> list) { + boolean failed = false; - void error(@Nullable final String errorMessage) { - if (errorMessage != null) { - Slog.e(TAG, errorMessage); + for (int i = 0; i < list.size(); i++) { + final ShortcutInfo si = list.get(i); + if (si.getRank() != i) { + failed = true; + Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + + " rank=" + si.getRank() + " but expected to be " + i); } - mLatch.countDown(); } + return failed; } } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 38cba4ca9e20..0b21487088ba 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -171,7 +171,7 @@ public class ShortcutService extends IShortcutService.Stub { static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10; @VisibleForTesting - static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15; + static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = Integer.MAX_VALUE; @VisibleForTesting static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; @@ -2590,7 +2590,6 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); ps.findAll(ret, query, cloneFlags); - return new ParceledListSlice<>(setReturnedByServer(ret)); } @@ -5078,6 +5077,17 @@ public class ShortcutService extends IShortcutService.Stub { } @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 ec784d0211dd..51cb995aac91 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.NonNull; 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.TypedXmlPullParser; 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 @@ class ShortcutUser { .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 fe19956ce8ce..2a0257dd2a20 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 @@ public class UserManagerService extends IUserManager.Stub { } @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 17ce386e3770..6824f7d8fa55 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 @@ public final class UserTypeDetails { */ 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 @@ public final class UserTypeDetails { @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 @@ public final class UserTypeDetails { this.mBadgeLabels = badgeLabels; this.mBadgeColors = badgeColors; this.mDarkThemeBadgeColors = darkThemeBadgeColors; + this.mSharesMediaWithParent = sharesMediaWithParent; } /** @@ -291,6 +300,13 @@ public final class UserTypeDetails { 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 @@ public final class UserTypeDetails { : 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 @@ public final class UserTypeDetails { 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 @@ public final class UserTypeDetails { 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 @@ public final class UserTypeDetails { 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 6aac0b2f3d0f..e8421a5d966d 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_GUEST; 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 @@ public final class UserTypeFactory { 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 @@ public final class UserTypeFactory { } /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE} + * configuration. + */ + // TODO(b/182396009): Add default restrictions, if needed for clone user type. + private static UserTypeDetails.Builder getDefaultTypeProfileClone() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_PROFILE_CLONE) + .setBaseType(FLAG_PROFILE) + .setMaxAllowedPerParent(1) + .setLabel(0) + .setDefaultRestrictions(null) + .setSharesMediaWithParent(true); + } + + /** * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED} * configuration. */ diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 27bf8a13766a..f0d54b4c0617 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -206,6 +206,12 @@ final class DefaultPermissionGrantPolicy { STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION); } + private static final Set<String> NEARBY_DEVICES_PERMISSIONS = new ArraySet<>(); + static { + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT); + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); + } + private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1; private static final String ACTION_TRACK = "com.android.fitness.TRACK"; @@ -733,14 +739,15 @@ final class DefaultPermissionGrantPolicy { PHONE_PERMISSIONS, SMS_PERMISSIONS, CAMERA_PERMISSIONS, SENSORS_PERMISSIONS, STORAGE_PERMISSIONS); grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId, - ALWAYS_LOCATION_PERMISSIONS, ACTIVITY_RECOGNITION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS, + ACTIVITY_RECOGNITION_PERMISSIONS); } } if (locationExtraPackageNames != null) { // Also grant location and activity recognition permission to location extra packages. for (String packageName : locationExtraPackageNames) { grantPermissionsToSystemPackage(pm, packageName, userId, - ALWAYS_LOCATION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS); grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId, ACTIVITY_RECOGNITION_PERMISSIONS); } @@ -809,7 +816,7 @@ final class DefaultPermissionGrantPolicy { // Companion devices grantSystemFixedPermissionsToSystemPackage(pm, CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, userId, - ALWAYS_LOCATION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS); // Ringtone Picker grantSystemFixedPermissionsToSystemPackage(pm, diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index cb3b5c9db7e7..44ff3eb4b942 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.Intent; 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 @@ public final class DomainVerificationUtils { 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 d0aa28b441a0..1b5bb95fd40b 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java index beebb3145018..0a6772bd8f6a 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java @@ -21,11 +21,13 @@ import static android.os.UserHandle.USER_SYSTEM; import android.annotation.IntDef; import android.content.Context; import android.content.IntentSender; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.boot.V1_0.IBootControl; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Binder; +import android.os.Environment; import android.os.IRecoverySystem; import android.os.IRecoverySystemProgressListener; import android.os.PowerManager; @@ -52,6 +54,7 @@ import libcore.io.IoUtils; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; import java.io.FileDescriptor; import java.io.FileWriter; import java.io.IOException; @@ -87,6 +90,12 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo private static final int SOCKET_CONNECTION_MAX_RETRY = 30; + static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp"; + static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count"; + + static final String LSKF_CAPTURED_TIMESTAMP_PREF = "lskf_captured_timestamp"; + static final String LSKF_CAPTURED_COUNT_PREF = "lskf_captured_count"; + private final Injector mInjector; private final Context mContext; @@ -127,7 +136,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo */ @IntDef({ ROR_NEED_PREPARATION, ROR_SKIP_PREPARATION_AND_NOTIFY, - ROR_SKIP_PREPARATION_NOT_NOTIFY }) + ROR_SKIP_PREPARATION_NOT_NOTIFY}) private @interface ResumeOnRebootActionsOnRequest {} /** @@ -139,7 +148,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo private @interface ResumeOnRebootActionsOnClear {} /** - * The error code for reboots initiated by resume on reboot clients. + * The error codes for reboots initiated by resume on reboot clients. */ private static final int REBOOT_ERROR_NONE = 0; private static final int REBOOT_ERROR_UNKNOWN = 1; @@ -156,11 +165,64 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE}) private @interface ResumeOnRebootRebootErrorCode {} + /** + * Manages shared preference, i.e. the storage used for metrics reporting. + */ + public static class PreferencesManager { + private static final String METRICS_DIR = "recovery_system"; + private static final String METRICS_PREFS_FILE = "RecoverySystemMetricsPrefs.xml"; + + protected final SharedPreferences mSharedPreferences; + private final File mMetricsPrefsFile; + + PreferencesManager(Context context) { + File prefsDir = new File(Environment.getDataSystemCeDirectory(USER_SYSTEM), + METRICS_DIR); + mMetricsPrefsFile = new File(prefsDir, METRICS_PREFS_FILE); + mSharedPreferences = context.getSharedPreferences(mMetricsPrefsFile, 0); + } + + /** Reads the value of a given key with type long. **/ + public long getLong(String key, long defaultValue) { + return mSharedPreferences.getLong(key, defaultValue); + } + + /** Reads the value of a given key with type int. **/ + public int getInt(String key, int defaultValue) { + return mSharedPreferences.getInt(key, defaultValue); + } + + /** Stores the value of a given key with type long. **/ + public void putLong(String key, long value) { + mSharedPreferences.edit().putLong(key, value).commit(); + } + + /** Stores the value of a given key with type int. **/ + public void putInt(String key, int value) { + mSharedPreferences.edit().putInt(key, value).commit(); + } + + /** Increments the value of a given key with type int. **/ + public synchronized void incrementIntKey(String key, int defaultInitialValue) { + int oldValue = getInt(key, defaultInitialValue); + putInt(key, oldValue + 1); + } + + /** Delete the preference file and cleanup all metrics storage. **/ + public void deletePrefsFile() { + if (!mMetricsPrefsFile.delete()) { + Slog.w(TAG, "Failed to delete metrics prefs"); + } + } + } + static class Injector { protected final Context mContext; + protected final PreferencesManager mPrefs; Injector(Context context) { mContext = context; + mPrefs = new PreferencesManager(context); } public Context getContext() { @@ -236,6 +298,14 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return -1; } + public PreferencesManager getMetricsPrefs() { + return mPrefs; + } + + public long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + public void reportRebootEscrowPreparationMetrics(int uid, @ResumeOnRebootActionsOnRequest int requestResult, int requestedClientCount) { FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_PREPARATION_REPORTED, uid, @@ -414,7 +484,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY) != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(android.Manifest.permission.REBOOT) - != PackageManager.PERMISSION_GRANTED) { + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller must have " + android.Manifest.permission.RECOVERY + " or " + android.Manifest.permission.REBOOT + " for resume on reboot."); } @@ -427,6 +497,12 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo pendingRequestCount = mCallerPendingRequest.size(); } + // Save the timestamp and request count for new ror request + PreferencesManager prefs = mInjector.getMetricsPrefs(); + prefs.putLong(packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX, + mInjector.getCurrentTimeMillis()); + prefs.incrementIntKey(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, 0); + mInjector.reportRebootEscrowPreparationMetrics(uid, requestResult, pendingRequestCount); } @@ -486,15 +562,31 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo } private void reportMetricsOnPreparedForReboot() { + long currentTimestamp = mInjector.getCurrentTimeMillis(); + List<String> preparedClients; synchronized (this) { preparedClients = new ArrayList<>(mCallerPreparedForReboot); } + // Save the timestamp & lskf capture count for lskf capture + PreferencesManager prefs = mInjector.getMetricsPrefs(); + prefs.putLong(LSKF_CAPTURED_TIMESTAMP_PREF, currentTimestamp); + prefs.incrementIntKey(LSKF_CAPTURED_COUNT_PREF, 0); + for (String packageName : preparedClients) { int uid = mInjector.getUidFromPackageName(packageName); + + int durationSeconds = -1; + long requestLskfTimestamp = prefs.getLong( + packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX, -1); + if (requestLskfTimestamp != -1 && currentTimestamp > requestLskfTimestamp) { + durationSeconds = (int) (currentTimestamp - requestLskfTimestamp) / 1000; + } + Slog.i(TAG, String.format("Reporting lskf captured, lskf capture takes %d seconds for" + + " package %s", durationSeconds, packageName)); mInjector.reportRebootEscrowLskfCapturedMetrics(uid, preparedClients.size(), - -1 /* duration */); + durationSeconds); } } @@ -541,6 +633,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo Slog.w(TAG, "Missing packageName when clearing lskf."); return false; } + // TODO(179105110) Clear the RoR metrics for the given packageName. @ResumeOnRebootActionsOnClear int action = updateRoRPreparationStateOnClear(packageName); switch (action) { @@ -659,10 +752,23 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo preparedClientCount = mCallerPreparedForReboot.size(); } - // TODO(b/179105110) report the true value of duration and counts + long currentTimestamp = mInjector.getCurrentTimeMillis(); + int durationSeconds = -1; + PreferencesManager prefs = mInjector.getMetricsPrefs(); + long lskfCapturedTimestamp = prefs.getLong(LSKF_CAPTURED_TIMESTAMP_PREF, -1); + if (lskfCapturedTimestamp != -1 && currentTimestamp > lskfCapturedTimestamp) { + durationSeconds = (int) (currentTimestamp - lskfCapturedTimestamp) / 1000; + } + + int requestCount = prefs.getInt(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, -1); + int lskfCapturedCount = prefs.getInt(LSKF_CAPTURED_COUNT_PREF, -1); + + Slog.i(TAG, String.format("Reporting reboot with lskf, package name %s, client count %d," + + " request count %d, lskf captured count %d, duration since lskf captured" + + " %d seconds.", packageName, preparedClientCount, requestCount, + lskfCapturedCount, durationSeconds)); mInjector.reportRebootEscrowRebootMetrics(errorCode, uid, preparedClientCount, - 1 /* request count */, slotSwitch, serverBased, - -1 /* duration */, 1 /* lskf capture count */); + requestCount, slotSwitch, serverBased, durationSeconds, lskfCapturedCount); } private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) { @@ -673,6 +779,9 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return false; } + // Clear the metrics prefs after a successful RoR reboot. + mInjector.getMetricsPrefs().deletePrefsFile(); + PowerManager pm = mInjector.getPowerManager(); pm.reboot(reason); return true; diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java index a7f3cdb8bcd2..c029bf53809a 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.Manifest; 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 @@ public class RotationResolverManagerService extends 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 @@ public class RotationResolverManagerService extends 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 @@ public class RotationResolverManagerService extends 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 000000000000..3ca8a5a1f554 --- /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 5d49663209b7..030bbd2bc652 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 { - - 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; +public interface ServiceWatcher { - 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; - } - /** - * Information on the service ServiceWatcher has selected as the best option for binding. + * 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 */ - @Immutable - public static final class BoundService implements Comparable<BoundService> { - - public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null, - false, null, -1); - - public final int version; - @Nullable - public final ComponentName component; - public final boolean serviceIsMultiuser; - public final int uid; - @Nullable - public final Bundle metadata; - - BoundService(ResolveInfo resolveInfo) { - Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null); - - 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; - } - - component = resolveInfo.serviceInfo.getComponentName(); - uid = resolveInfo.serviceInfo.applicationInfo.uid; - } - - 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; - } - - public @Nullable String getPackageName() { - return component != null ? component.getPackageName() : null; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof BoundService)) { - return false; - } - BoundService that = (BoundService) o; - return version == that.version && uid == that.uid - && Objects.equals(component, that.component); - } + interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> { + /** Invoked when a service is bound. Run on the ServiceWatcher thread. */ + void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException; - @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; - } - - @Override - public String toString() { - if (component == null) { - return "none"; - } else { - return component.toShortString() + "@" + version + "[u" - + UserHandle.getUserId(uid) + "]"; - } - } - } - - 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; + /** Invoked when a service is unbound. Run on the ServiceWatcher thread. */ + void onUnbind(); } /** - * Returns true if there is at least one component that could satisfy the ServiceWatcher's - * constraints. + * A listener for when a {@link ServiceSupplier} decides that the current service has changed. */ - 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; + interface ServiceChangedListener { + /** + * Should be invoked when the current service may have changed. + */ + void onServiceChanged(); } /** - * Starts the process of determining the best matching service and maintaining a binding to it. + * 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 */ - 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); + 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(); - // 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(); + /** + * 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); - mRegistered = true; + /** + * 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(); - mHandler.post(() -> onBestServiceChanged(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(); } /** - * Stops the process of determining the best matching service and releases any binding. + * Information on the service selected as the best option for binding. */ - public void unregister() { - mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal, - ServiceWatcher.this)); - } - - private void unregisterInternal() { - Preconditions.checkState(mRegistered); + class BoundServiceInfo { - mRegistered = false; + protected final @Nullable String mAction; + protected final int mUid; + protected final ComponentName mComponentName; - 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; + protected BoundServiceInfo(String action, ResolveInfo resolveInfo) { + this(action, resolveInfo.serviceInfo.applicationInfo.uid, + resolveInfo.serviceInfo.getComponentName()); } - mTargetService = newServiceInfo; - if (mTargetService.equals(BoundService.NONE)) { - return; + protected BoundServiceInfo(String action, int uid, ComponentName componentName) { + mAction = action; + mUid = uid; + mComponentName = Objects.requireNonNull(componentName); } - 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); + /** Returns the action associated with this bound service. */ + public @Nullable String getAction() { + return mAction; } - } - - @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()); + /** Returns the component of this bound service. */ + public ComponentName getComponentName() { + return mComponentName; } - final BoundService boundService = mPendingBinds.remove(component); - if (boundService == null) { - return; + /** Returns the user id for this bound service. */ + public @UserIdInt int getUserId() { + return UserHandle.getUserId(mUid); } - 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 boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BoundServiceInfo)) { + return false; } - } - } - - @Override - public final void onServiceDisconnected(ComponentName component) { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - if (mBinder == null) { - return; + BoundServiceInfo that = (BoundServiceInfo) o; + return mUid == that.mUid + && Objects.equals(mAction, that.mAction) + && mComponentName.equals(that.mComponentName); } - if (D) { - Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString()); + @Override + public final int hashCode() { + return Objects.hash(mAction, mUid, mComponentName); } - mBinder = null; - if (mOnUnbind != null) { - mOnUnbind.run(); + @Override + public String toString() { + if (mComponentName == null) { + return "none"; + } else { + return mUid + "/" + mComponentName.flattenToShortString(); + } } } - @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); + /** + * Creates a new ServiceWatcher instance. + */ + 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); } - void onUserUnlocked(@UserIdInt int userId) { - if (userId == mCurrentUserId) { - onBestServiceChanged(false); - } + /** + * Creates a new ServiceWatcher instance that runs on the given handler. + */ + 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); } /** - * Runs the given function asynchronously if and only if currently connected. Suppresses any - * RemoteException thrown during execution. + * Returns true if there is at least one service that the ServiceWatcher could hypothetically + * bind to, as selected by the {@link ServiceSupplier}. */ - public final void runOnBinder(BinderRunner runner) { - mHandler.post(() -> { - if (mBinder == null) { - runner.onError(); - return; - } + boolean checkServiceResolves(); - 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(); - } - }); - } + /** + * 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(); - private String getLogPrefix() { - return "[" + mIntent.getAction() + "]"; - } + /** + * 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(); - @Override - public String toString() { - return mTargetService.toString(); - } + /** + * 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); /** - * Dump for debugging. + * Dumps ServiceWatcher information. */ - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("target service=" + mTargetService); - pw.println("connected=" + (mBinder != null)); - } -} + 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 000000000000..e718ba3b17cf --- /dev/null +++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.servicewatcher; + +import static android.content.Context.BIND_AUTO_CREATE; +import static android.content.Context.BIND_NOT_FOREGROUND; +import static android.content.Context.BIND_NOT_VISIBLE; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageMonitor; +import com.android.internal.util.Preconditions; +import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo; +import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener; + +import java.io.PrintWriter; +import java.util.Objects; + +/** + * Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows + * us to store the generic relationship between the service supplier and the service listener, while + * hiding the generics from clients, simplifying the API. + */ +class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher, + ServiceChangedListener { + + static final String TAG = "ServiceWatcher"; + static final boolean D = Log.isLoggable(TAG, Log.DEBUG); + + static final long RETRY_DELAY_MS = 15 * 1000; + + final Context mContext; + final Handler mHandler; + final String mTag; + final ServiceSupplier<TBoundServiceInfo> mServiceSupplier; + final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener; + + private final PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + return true; + } + + @Override + public void onSomePackagesChanged() { + onServiceChanged(false); + } + }; + + @GuardedBy("this") + private boolean mRegistered = false; + @GuardedBy("this") + private MyServiceConnection mServiceConnection = new MyServiceConnection(null); + + ServiceWatcherImpl(Context context, Handler handler, String tag, + ServiceSupplier<TBoundServiceInfo> serviceSupplier, + ServiceListener<? super TBoundServiceInfo> serviceListener) { + mContext = context; + mHandler = handler; + mTag = tag; + mServiceSupplier = serviceSupplier; + mServiceListener = serviceListener; + } + + @Override + public boolean checkServiceResolves() { + return mServiceSupplier.hasMatchingService(); + } + + @Override + public synchronized void register() { + Preconditions.checkState(!mRegistered); + + mRegistered = true; + mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler); + mServiceSupplier.register(this); + + onServiceChanged(false); + } + + @Override + public synchronized void unregister() { + Preconditions.checkState(mRegistered); + + mServiceSupplier.unregister(); + mPackageMonitor.unregister(); + mRegistered = false; + + onServiceChanged(false); + } + + @Override + public synchronized void onServiceChanged() { + onServiceChanged(false); + } + + @Override + public synchronized void runOnBinder(BinderOperation operation) { + MyServiceConnection serviceConnection = mServiceConnection; + mHandler.post(() -> serviceConnection.runOnBinder(operation)); + } + + synchronized void onServiceChanged(boolean forceRebind) { + TBoundServiceInfo newBoundServiceInfo; + if (mRegistered) { + newBoundServiceInfo = mServiceSupplier.getServiceInfo(); + } else { + newBoundServiceInfo = null; + } + + if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(), + newBoundServiceInfo)) { + MyServiceConnection oldServiceConnection = mServiceConnection; + MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo); + mServiceConnection = newServiceConnection; + mHandler.post(() -> { + oldServiceConnection.unbind(); + newServiceConnection.bind(); + }); + } + } + + @Override + public String toString() { + MyServiceConnection serviceConnection; + synchronized (this) { + serviceConnection = mServiceConnection; + } + + return serviceConnection.getBoundServiceInfo().toString(); + } + + @Override + public void dump(PrintWriter pw) { + MyServiceConnection serviceConnection; + synchronized (this) { + serviceConnection = mServiceConnection; + } + + pw.println("target service=" + serviceConnection.getBoundServiceInfo()); + pw.println("connected=" + serviceConnection.isConnected()); + } + + // runs on the handler thread, and expects most of it's methods to be called from that thread + private class MyServiceConnection implements ServiceConnection { + + private final @Nullable TBoundServiceInfo mBoundServiceInfo; + + // volatile so that isConnected can be called from any thread easily + private volatile @Nullable IBinder mBinder; + private @Nullable Runnable mRebinder; + + MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) { + mBoundServiceInfo = boundServiceInfo; + } + + // may be called from any thread + @Nullable TBoundServiceInfo getBoundServiceInfo() { + return mBoundServiceInfo; + } + + // may be called from any thread + boolean isConnected() { + return mBinder != null; + } + + void bind() { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + if (mBoundServiceInfo == null) { + return; + } + + Log.i(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo); + + Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent( + mBoundServiceInfo.getComponentName()); + if (!mContext.bindServiceAsUser(bindIntent, this, + BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE, + mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) { + Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later"); + mRebinder = this::bind; + mHandler.postDelayed(mRebinder, RETRY_DELAY_MS); + } else { + mRebinder = null; + } + } + + void unbind() { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + if (mBoundServiceInfo == null) { + return; + } + + if (D) { + Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo); + } + + if (mRebinder != null) { + mHandler.removeCallbacks(mRebinder); + mRebinder = null; + } else { + mContext.unbindService(this); + } + + onServiceDisconnected(mBoundServiceInfo.getComponentName()); + } + + void runOnBinder(BinderOperation operation) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + if (mBinder == null) { + operation.onError(); + return; + } + + try { + operation.run(mBinder); + } catch (RuntimeException | RemoteException e) { + // binders may propagate some specific non-RemoteExceptions from the other side + // through the binder as well - we cannot allow those to crash the system server + Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e); + operation.onError(); + } + } + + @Override + public final void onServiceConnected(ComponentName component, IBinder binder) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + Preconditions.checkState(mBinder == null); + + if (D) { + Log.d(TAG, "[" + mTag + "] connected to " + component.toShortString()); + } + + mBinder = binder; + + if (mServiceListener != null) { + try { + mServiceListener.onBind(binder, mBoundServiceInfo); + } catch (RuntimeException | RemoteException e) { + // binders may propagate some specific non-RemoteExceptions from the other side + // through the binder as well - we cannot allow those to crash the system server + Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e); + } + } + } + + @Override + public final void onServiceDisconnected(ComponentName component) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + if (mBinder == null) { + return; + } + + if (D) { + Log.d(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo); + } + + mBinder = null; + if (mServiceListener != null) { + mServiceListener.onUnbind(); + } + } + + @Override + public final void onBindingDied(ComponentName component) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + Log.i(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died"); + + onServiceChanged(true); + } + + @Override + public final void onNullBinding(ComponentName component) { + Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding"); + } + } +} diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java index dedb3ac6397d..ff6c2f551bb3 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.PackageManager; 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 @@ public final class StorageSessionController { 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 final class StorageSessionController { 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 @@ public final class StorageSessionController { 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 @@ public final class StorageSessionController { return; } String sessionId = vol.getId(); - int userId = vol.getMountUserId(); + int userId = getConnectionUserIdForVolume(vol); StorageUserConnection connection = null; synchronized (mLock) { @@ -191,7 +218,7 @@ public final class StorageSessionController { 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/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index eefa045abe1b..27e2ee585a20 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -228,7 +228,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { private void enforceSuggestExternalTimePermission() { // We don't expect a call from system server, so simply enforce calling permission. mContext.enforceCallingPermission( - android.Manifest.permission.SET_TIME, + android.Manifest.permission.SUGGEST_EXTERNAL_TIME, "suggest time from external source"); } diff --git a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java index a7767a4fbd66..23e3eba68c76 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.ComponentName; 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 @@ public final class TimeZoneUpdateIdler extends JobService { @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 4fa920e5b7d2..f054c5756e20 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 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { @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 cc815dc61886..e116a8742208 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_RESU 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 @@ abstract class LocationTimeZoneProvider implements Dumpable { } /** - * 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 @@ abstract class LocationTimeZoneProvider implements Dumpable { // 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 @@ abstract class LocationTimeZoneProvider implements Dumpable { 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 @@ abstract class LocationTimeZoneProvider implements Dumpable { } } - 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 0b51488280e0..6c3f016588f8 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.service.timezone.TimeZoneProviderSuggestion; 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 @@ import java.util.function.Predicate; * 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 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { 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 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { 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 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { } } - private void onUnbind() { + @Override + public void onUnbind() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { @@ -199,7 +173,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { 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 index cab5ad25c54e..951e9d05a150 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 @@ package com.android.server.timezonedetector.location; 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 000000000000..0f4367dddc6e --- /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 e84ee672bf0f..cd840589475a 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -16,17 +16,24 @@ package com.android.server.vibrator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.CombinedVibrationEffect; import android.os.IBinder; import android.os.SystemClock; import android.os.VibrationAttributes; import android.os.VibrationEffect; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; +import java.util.function.Function; /** Represents a vibration request to the vibrator service. */ final class Vibration { @@ -61,6 +68,7 @@ final class Vibration { public final String opPkg; public final String reason; public final IBinder token; + public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); /** The actual effect to be played. */ @Nullable @@ -113,17 +121,70 @@ final class Vibration { } /** - * 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))); + } + } + + /** + * 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; } - mEffect = newEffect; } /** Return true is current status is different from {@link Status#RUNNING}. */ @@ -272,57 +333,62 @@ final class Vibration { private void dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect) { final long token = proto.start(fieldId); - if (effect instanceof VibrationEffect.OneShot) { - dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect); - } else if (effect instanceof VibrationEffect.Waveform) { - dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect); - } else if (effect instanceof VibrationEffect.Prebaked) { - dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect); - } else if (effect instanceof VibrationEffect.Composed) { - dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect); + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + for (VibrationEffectSegment segment : composed.getSegments()) { + dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment); } + proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.OneShot effect) { + VibrationEffectSegment segment) { final long token = proto.start(fieldId); - proto.write(OneShotProto.DURATION, (int) effect.getDuration()); - proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude()); + if (segment instanceof StepSegment) { + dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment); + } else if (segment instanceof RampSegment) { + dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment); + } else if (segment instanceof PrebakedSegment) { + dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment); + } else if (segment instanceof PrimitiveSegment) { + dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment); + } proto.end(token); } - private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Waveform effect) { + private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment 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(StepSegmentProto.DURATION, segment.getDuration()); + proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude()); + proto.write(StepSegmentProto.FREQUENCY, segment.getFrequency()); + proto.end(token); + } + + private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) { + final long token = proto.start(fieldId); + proto.write(RampSegmentProto.DURATION, segment.getDuration()); + proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude()); + proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude()); + proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequency()); + proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequency()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Prebaked effect) { + PrebakedSegment 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.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.Composed effect) { + PrimitiveSegment segment) { 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 10393f682279..f481772d7a7f 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 @@ package com.android.server.vibrator; 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 @@ final class VibrationScaler { } /** - * 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 @@ final class VibrationScaler { * 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 @@ final class VibrationScaler { 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 b90408fe5371..18063de9f083 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.SystemClock; 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { 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 (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); + } } - } else if (effect instanceof VibrationEffect.Composed) { - VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; - duration = controller.on(composed, mVibration.id); + duration = controller.on(primitives, mVibration.id); if (duration > 0) { nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, controller)); @@ -713,33 +725,22 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { /** 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @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 < 0) { - return Arrays.asList(new VibratorOffStep(nextStartTime, controller)); + if (nextIndex >= effect.getSegments().size()) { + nextIndex = effect.getRepeatIndex(); } - 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 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { 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 e3dc70b41c0d..66200b32ac0c 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.Binder; 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 @@ final class VibratorController { * 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 @@ final class VibratorController { * * @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 @@ final class VibratorController { } /** - * 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 @@ final class VibratorController { 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 @@ final class VibratorController { } /** 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 @@ final class VibratorController { } /** 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 c9751bb7abe4..5fd1d7a6e1dc 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.VibrationAttributes; 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + @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 class VibratorManagerService extends IVibratorManagerService.Stub { 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 e6d37b60882e..b947c8883d07 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 @@ final class AccessibilityController { 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 7c236617d14f..eadfbe2c4ac0 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 46913eb42e3a..29c5cec59789 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.Watchdog; 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 6d2410578ced..6072a0678d4d 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @VisibleForTesting - @Nullable FixedRotationAnimationController getFixedRotationAnimationController() { - return mFixedRotationAnimationController; + @Nullable FadeRotationAnimationController getFadeRotationAnimationController() { + return mFadeRotationAnimationController; } void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) { @@ -1601,13 +1601,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * * @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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp }, 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 d0e4c40e3416..6046cc61258f 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 @@ public class DisplayRotation { }, true /* traverseTopToBottom */); mSeamlessRotationCount = 0; mRotatingSeamlessly = false; - mDisplayContent.finishFixedRotationAnimationIfPossible(); + mDisplayContent.finishFadeRotationAnimationIfPossible(); } private void prepareSeamlessRotation() { @@ -717,7 +717,7 @@ public class DisplayRotation { "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 000000000000..5ee692806349 --- /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 aa7317022794..000000000000 --- 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 b31c2e462766..20216c3afcd4 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 @@ class KeyguardController { 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 4ab5cd6a8d23..b1e12b685fb4 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 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter { 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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index d81181d46417..20c0d4189ad8 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1573,6 +1573,10 @@ class RecentTasks { } else if (document || trIsDocument) { // Only one of these is a document. Not the droid we're looking for. continue; + } else if (multiTasksAllowed) { + // Neither is a document, but the new task supports multiple tasks so keep the + // existing task + continue; } } return i; diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 64ff1084a6b8..bd8485470219 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 @@ public class RecentsAnimationController implements DeathRecipient { ? 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 @@ public class RecentsAnimationController implements DeathRecipient { 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 @@ public class RecentsAnimationController implements DeathRecipient { 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 @@ public class RecentsAnimationController implements DeathRecipient { 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 8915eba3d509..5af44317b8c1 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 @@ class TaskSnapshotController { 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 @@ class TaskSnapshotController { }); } + /** 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 57394d6d858e..6b1071c9e84d 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 class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6d88387fe25c..6da350bbedf2 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } 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 ebbebbb702d8..0c80f866ba7f 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 @@ class WindowStateAnimator { 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 f60b35499013..7f8168af944a 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 @@ static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong ptr) { 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 @@ static const JNINativeMethod method_table[] = { {"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 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env 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 aed13b263a7f..56e2385ca548 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { } 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 @@ class ActiveAdmin { 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 @@ class ActiveAdmin { 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 283895bb53e2..ca56e9bf36ce 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 a88f2b43b909..932e99783c83 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -377,6 +377,7 @@ void IncrementalService::onDump(int fd) { 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 @@ void IncrementalService::onDump(int fd) { } bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) { + if (!ifs.dataLoaderStub) { + return false; + } if (ifs.dataLoaderStub->isSystemDataLoader()) { return true; } @@ -439,6 +443,8 @@ void IncrementalService::onSystemReady() { 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 @@ void IncrementalService::onSystemReady() { 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 @@ bool IncrementalService::startLoading(StorageId storageId, setUidReadTimeouts(storageId, std::move(perUidReadTimeouts)); } + IfsMountPtr ifs; + DataLoaderStubPtr dataLoaderStub; + // Re-initialize DataLoader. - std::unique_lock l(mLock); - const auto ifs = getIfsLocked(storageId); - if (!ifs) { - return false; + { + ifs = getIfs(storageId); + if (!ifs) { + return false; + } + + std::unique_lock l(ifs->lock); + dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr); } - if (ifs->dataLoaderStub) { - ifs->dataLoaderStub->cleanupResources(); - ifs->dataLoaderStub = {}; + + if (dataLoaderStub) { + dataLoaderStub->cleanupResources(); + dataLoaderStub = {}; } - l.unlock(); - // DataLoader. - auto dataLoaderStub = - prepareDataLoader(*ifs, std::move(dataLoaderParams), std::move(statusListener), - healthCheckParams, std::move(healthListener)); - CHECK(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 @@ bool IncrementalService::startLoading(StorageId storageId, << 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 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { } 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 @@ void IncrementalService::disallowReadLogs(StorageId storageId) { return; } - setStorageParams(storageId, /*enableReadLogs=*/false); + disableReadLogsLocked(*ifs); } int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) { @@ -764,61 +787,66 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog 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; - } + std::unique_lock l(ifs->lock); + if (!enableReadLogs) { + return disableReadLogsLocked(*ifs); + } - // 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); - } + if (!ifs->readLogsAllowed()) { + LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId; + return -EPERM; + } - // 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); - } + 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) << "setStorageParams failed, readlogs can't be enabled at this time, " - "storageId: " - << storageId; - 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; } - if (auto status = applyStorageParams(ifs, enableReadLogs); !status.isOk()) { - LOG(ERROR) << "applyStorageParams failed: " << status.toString8(); + 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 @@ binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enab 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 @@ void IncrementalService::setUidReadTimeouts(StorageId storage, 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 @@ void IncrementalService::clearUidReadTimeouts(StorageId storage) { 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; } - - // Still loading? - const auto state = isMountFullyLoaded(storage); - if (int(state) < 0) { + if (state.error) { // Something is wrong, abort. - return clearUidReadTimeouts(storage); + return false; } - if (state == incfs::LoadingState::Full) { - // Fully loaded, check readLogs collection. - const auto ifs = getIfs(storage); - if (!ifs->readLogsEnabled()) { - return clearUidReadTimeouts(storage); - } + // Still loading? + 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 @@ bool IncrementalService::mountExistingImage(std::string_view root) { 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 @@ void IncrementalService::runCmdLooper() { } } -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 @@ void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderPara 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 @@ bool IncrementalService::registerStorageHealthListener( 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 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { 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 @@ bool IncrementalService::removeTimedJobs(TimedQueueWrapper& timedQueue, MountId 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 @@ void IncrementalService::getMetrics(StorageId storageId, android::os::Persistabl } 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 @@ void IncrementalService::DataLoaderStub::cleanupResources() { resetHealthControl(); mService.removeTimedJobs(*mService.mTimedQueue, mId); } + mService.removeIfsStateCallbacks(mId); requestDestroy(); @@ -2758,7 +2894,7 @@ void IncrementalService::DataLoaderStub::registerForPendingReads() { 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 4a5db062e3c5..a8f32dec824e 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -74,6 +74,17 @@ using StorageLoadingProgressListener = ::android::sp<IStorageLoadingProgressList 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 54bc95dc213b..de8822dbf105 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -140,9 +140,7 @@ public: 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 @@ public: 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 @@ public: 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 @@ TEST_F(IncrementalServiceTest, testDataLoaderOnRestart) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) { 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndTimedOut) { 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndNoTimedOutForSy 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndNoTimedOutForSy 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndNoTimedOutForSy 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndNewInstall) { 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 @@ TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerFailsToGetProg 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) { 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // 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 b01e42516358..800f7addbd65 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -83,3 +83,15 @@ filegroup { "//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 d5eda203e42f..dce853afc763 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 @@ class DomainVerificationCollectorTest { } 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 7e25901301aa..1b0a305b5bdd 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 @@ class DomainVerificationEnforcerTest { 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 0e74b65d25d5..ef79b08aa1c5 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 @@ class DomainVerificationManagerApiTest { }, 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 fe3672d06bc0..0ce16e6b6af2 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.junit.Test 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 @@ class DomainVerificationPackageTest { }, 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 377bae15e2d5..b7c69226ded2 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 @@ class DomainVerificationSettingsMutationTest { 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 44c1b8f3fbb9..54648ab67422 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 @@ class DomainVerificationUserStateOverrideTest { }, 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 7925b69852ba..0fcda819d83c 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 @@ public class JobStatusTest { 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 @@ public class JobStatusTest { 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 f73af535f452..4bab8e51874c 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 @@ public class QuotaControllerTest { // 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; } @@ -5415,9 +5416,8 @@ public class QuotaControllerTest { inOrder.verify(mJobSchedulerService, timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(); - // Top and bg EJs should still be allowed to run since they started before the app ran - // out of quota. - assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); + // Top should still be "in quota" since it started before the app ran on top out of quota. + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse( jobUnstarted.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); @@ -5466,7 +5466,7 @@ public class QuotaControllerTest { // wasn't started. Top should remain in quota since it started when the app was in TOP. assertTrue(jobTop2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); - assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); trackJobs(jobBg2); assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse( diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java index 059744388197..ef4864620d22 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 @@ package com.android.server.location.injector; 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 @@ public class FakeLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper @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 6156ba9359cd..28da027669b8 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.filters.SmallTest; 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 @@ public class SystemLocationPowerSaveModeHelperTest { 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 1f102ac32a8e..ae70dadba041 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 @@ public class TestInjector implements Injector { 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 1b58e924dd6a..24b85f056731 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 androidx.test.runner.AndroidJUnit4; 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 @@ public class LocationProviderManagerTest { 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 c3cca64154d6..04e0151e619a 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 android.util.Log; 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 @@ public class StationaryThrottlingLocationProviderTest { 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/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index b9aa554aa0ac..5761958ea89d 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -74,6 +74,9 @@ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> <uses-permission android:name="android.permission.DUMP"/> <uses-permission android:name="android.permission.READ_DREAM_STATE"/> diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 1b8ab2175458..2f0d71a2a579 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -58,6 +58,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; /** * Tests for {@link com.android.server.apphibernation.AppHibernationService} @@ -116,8 +117,8 @@ public final class AppHibernationServiceTest { mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); UserInfo userInfo = addUser(USER_ID_1); - mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); mAppHibernationService.mIsServiceEnabled = true; } @@ -150,8 +151,8 @@ public final class AppHibernationServiceTest { throws RemoteException { // WHEN a new user is added and a package from the user is hibernated UserInfo user2 = addUser(USER_ID_2); - mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); // THEN the new user's package is hibernated @@ -188,8 +189,8 @@ public final class AppHibernationServiceTest { // GIVEN an unlocked user with all packages installed UserInfo userInfo = addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3}); - mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); // WHEN packages are hibernated for the user mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); @@ -259,6 +260,12 @@ public final class AppHibernationServiceTest { } @Override + public Executor getBackgroundExecutor() { + // Just execute immediately in tests. + return r -> r.run(); + } + + @Override public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { return Mockito.mock(HibernationStateDiskStore.class); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java new file mode 100644 index 000000000000..6cdac1af87eb --- /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/content/OWNERS b/services/tests/servicestests/src/com/android/server/content/OWNERS new file mode 100644 index 000000000000..6264a1427c7f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/content/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/content/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 576f9c23e350..557d07c638f1 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @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 @@ public class DevicePolicyManagerTest extends DpmTestBase { // 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/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 50ba761cef10..ee9de07a15d2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -107,7 +107,7 @@ public class ArcInitiationActionFromAvrTest { } @Override - Looper getServiceLooper() { + protected Looper getServiceLooper() { return mTestLooper.getLooper(); } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index aa5bc933002d..d5df07102d73 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -108,7 +108,7 @@ public class ArcTerminationActionFromAvrTest { } @Override - Looper getServiceLooper() { + protected Looper getServiceLooper() { return mTestLooper.getLooper(); } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 41f4a1e2d94c..8b23be511ac0 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -91,7 +91,7 @@ public class DetectTvSystemAudioModeSupportActionTest { } @Override - Looper getServiceLooper() { + protected Looper getServiceLooper() { return mTestLooper.getLooper(); } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index b3ee18ddff55..ee1a85745701 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -19,21 +19,35 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; +import static com.android.server.hdmi.Constants.ABORT_REFUSED; +import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BACKUP_1; import static com.android.server.hdmi.Constants.ADDR_BACKUP_2; +import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3; import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; +import static com.android.server.hdmi.Constants.HANDLED; +import static com.android.server.hdmi.Constants.MESSAGE_STANDBY; +import static com.android.server.hdmi.Constants.NOT_HANDLED; + +import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import android.content.Context; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + import android.hardware.hdmi.HdmiControlManager; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Binder; @@ -44,6 +58,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.SystemService; import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; import junit.framework.TestCase; @@ -53,6 +68,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.Optional; /** Tests for {@link com.android.server.hdmi.HdmiCecController} class. */ @@ -63,27 +79,7 @@ public class HdmiCecControllerTest { private FakeNativeWrapper mNativeWrapper; - private class MyHdmiControlService extends HdmiControlService { - - MyHdmiControlService(Context context) { - super(context); - } - - @Override - Looper getIoLooper() { - return mMyLooper; - } - - @Override - Looper getServiceLooper() { - return mMyLooper; - } - - @Override - int getCecVersion() { - return mCecVersion; - } - } + private HdmiControlService mHdmiControlServiceSpy; private HdmiCecController mHdmiCecController; private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; @@ -101,12 +97,39 @@ public class HdmiCecControllerTest { @Before public void SetUp() { mMyLooper = mTestLooper.getLooper(); - mMyLooper = mTestLooper.getLooper(); - HdmiControlService hdmiControlService = new MyHdmiControlService( - InstrumentationRegistry.getTargetContext()); + + mHdmiControlServiceSpy = spy(new HdmiControlService( + InstrumentationRegistry.getTargetContext())); + doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper(); + doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper(); + doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion(); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); + mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( - hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + } + + /** Additional setup for tests for onMessage + * Adds a local playback device and allocates addresses + */ + public void setUpForOnMessageTest() { + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + + HdmiCecLocalDevicePlayback playbackDevice = + new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy); + playbackDevice.init(); + + ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); + localDevices.add(playbackDevice); + + mHdmiControlServiceSpy.initService(); + mHdmiControlServiceSpy.allocateLogicalAddress(localDevices, + HdmiControlService.INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + + mTestLooper.dispatchAll(); } /** Tests for {@link HdmiCecController#allocateLogicalAddress} */ @@ -119,7 +142,6 @@ public class HdmiCecControllerTest { @Test public void testAllocateLogicalAddress_TvDeviceNonPreferredNotOccupied() { - mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_TV, mLogicalAddress); @@ -308,4 +330,90 @@ public class HdmiCecControllerTest { TestCase.assertEquals(Optional.of(callerUid), uidReadingRunnable.getWorkSourceUid()); TestCase.assertEquals(runnerUid, Binder.getCallingWorkSourceUid()); } + + @Test + public void onMessage_broadcastMessage_doesNotSendFeatureAbort() { + setUpForOnMessageTest(); + + doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_BROADCAST); + + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + assertFalse("No <Feature Abort> messages should be sent", + mNativeWrapper.getResultMessages().stream().anyMatch( + message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)); + } + + @Test + public void onMessage_notTheDestination_doesNotSendFeatureAbort() { + setUpForOnMessageTest(); + + doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + assertFalse("No <Feature Abort> messages should be sent", + mNativeWrapper.getResultMessages().stream().anyMatch( + message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)); + } + + @Test + public void onMessage_handledMessage_doesNotSendFeatureAbort() { + setUpForOnMessageTest(); + + doReturn(HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_PLAYBACK_1); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + assertFalse("No <Feature Abort> messages should be sent", + mNativeWrapper.getResultMessages().stream().anyMatch( + message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)); + } + + @Test + public void onMessage_unhandledMessage_sendsFeatureAbortUnrecognizedOpcode() { + setUpForOnMessageTest(); + + doReturn(NOT_HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_PLAYBACK_1); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_UNRECOGNIZED_OPCODE); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); + } + + @Test + public void onMessage_sendsFeatureAbortWithRequestedOperand() { + setUpForOnMessageTest(); + + doReturn(ABORT_REFUSED).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_PLAYBACK_1); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_REFUSED); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 6bb148d43a57..38a44c6c0d55 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -20,7 +20,6 @@ import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; -import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; @@ -248,7 +247,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true); HdmiCecMessage messageGive = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -262,45 +262,25 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage messageGive = HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @Test public void handleRequestShortAudioDescriptor_featureDisabled() throws Exception { - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, - Constants.ABORT_REFUSED); - mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false); - assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( MESSAGE_REQUEST_SAD_LCPM)) - .isTrue(); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + .isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleRequestShortAudioDescriptor_samOff() throws Exception { - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, - Constants.ABORT_NOT_IN_CORRECT_MODE); - mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false); - assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE); } // Testing device has sadConfig.xml @@ -315,10 +295,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.ABORT_UNABLE_TO_DETERMINE); mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); - assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -335,17 +314,18 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); // Check if correctly turned on mNativeWrapper.clearResultMessages(); expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isFalse(); @@ -365,7 +345,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildSetSystemAudioMode( ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); @@ -373,7 +353,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isTrue(); @@ -441,7 +421,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { public void handleActiveSource_updateActiveSource() throws Exception { HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource)) .isTrue(); @@ -513,17 +494,10 @@ public class HdmiCecLocalDeviceAudioSystemTest { public void handleRequestArcInitiate_isNotDirectConnectedToTv() throws Exception { HdmiCecMessage message = HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_ARC_INITIATION, - Constants.ABORT_NOT_IN_CORRECT_MODE); mNativeWrapper.setPhysicalAddress(0x1100); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE); } @Test @@ -533,7 +507,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { mNativeWrapper.setPhysicalAddress(0x1000); mHdmiCecLocalDeviceAudioSystem.removeAction(ArcInitiationActionFromAvr.class); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class)) .isNotEmpty(); @@ -548,7 +523,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM); mHdmiCecLocalDeviceAudioSystem.removeAction(ArcTerminationActionFromAvr.class); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class)) .isNotEmpty(); @@ -567,7 +543,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_REQUEST_ARC_TERMINATION, Constants.ABORT_NOT_IN_CORRECT_MODE); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } @@ -576,17 +553,10 @@ public class HdmiCecLocalDeviceAudioSystemTest { public void handleRequestArcInit_arcIsNotSupported() throws Exception { HdmiCecMessage message = HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_ARC_INITIATION, - Constants.ABORT_UNRECOGNIZED_OPCODE); mArcSupport = false; - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE); } @Test @@ -612,7 +582,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, Constants.ABORT_REFUSED); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)) + .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -629,7 +600,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -642,7 +614,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource()) .isEqualTo(expectedActiveSource); @@ -659,7 +632,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { ActiveSource expectedActiveSource = ActiveSource.of(ADDR_PLAYBACK_1, SELF_PHYSICAL_ADDRESS); int expectedLocalActivePort = Constants.CEC_SWITCH_HOME; - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource()) .isEqualTo(expectedActiveSource); @@ -677,7 +651,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildRoutingInformation( ADDR_AUDIO_SYSTEM, HDMI_1_PHYSICAL_ADDRESS); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -691,7 +666,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } @@ -727,18 +703,15 @@ public class HdmiCecLocalDeviceAudioSystemTest { int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, mute); - HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED); HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); mNativeWrapper.clearResultMessages(); - boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expected); - assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort); - assertThat(handled).isTrue(); } @Test @@ -758,18 +731,15 @@ public class HdmiCecLocalDeviceAudioSystemTest { int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, mute); - HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED); HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); mNativeWrapper.clearResultMessages(); - boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus)) + .isEqualTo(Constants.ABORT_REFUSED); mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected); - assertThat(handled).isTrue(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 1a6bad8b29cf..80da6961cf16 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -113,7 +113,7 @@ public class HdmiCecLocalDevicePlaybackTest { } @Override - boolean isStandbyMessageReceived() { + protected boolean isStandbyMessageReceived() { return mStandby; } @@ -184,7 +184,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -205,7 +206,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -226,7 +228,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -247,7 +250,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -270,7 +274,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); @@ -293,7 +298,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); @@ -309,7 +315,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( 0x5000); @@ -329,7 +336,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -349,7 +357,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -368,7 +377,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); } @@ -383,7 +393,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isFalse(); } @@ -399,7 +410,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mStandby).isFalse(); } @@ -461,7 +473,8 @@ public class HdmiCecLocalDevicePlaybackTest { mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest"); mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( 0x5000); @@ -481,7 +494,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -501,7 +515,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -520,7 +535,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); } @@ -535,7 +551,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isFalse(); } @@ -551,7 +568,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mStandby).isFalse(); } @@ -606,7 +624,8 @@ public class HdmiCecLocalDevicePlaybackTest { public void handleSetStreamPath() { HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); } @Test @@ -616,7 +635,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildSetSystemAudioMode( Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true); - assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue(); } @@ -629,7 +649,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildSetSystemAudioMode( Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, false); - assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue(); } @@ -640,7 +661,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildReportSystemAudioMode( Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, true); - assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue(); } @@ -883,7 +905,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); @@ -900,7 +923,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE); mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); @@ -918,7 +942,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); @@ -931,7 +956,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW); mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isTrue(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); @@ -996,12 +1022,14 @@ public class HdmiCecLocalDevicePlaybackTest { // 1. DUT is <AS>. HdmiCecMessage message1 = HdmiCecMessageBuilder.buildActiveSource( mHdmiCecLocalDevicePlayback.mAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mStandby).isFalse(); // 2. DUT loses <AS> and goes to sleep. HdmiCecMessage message2 = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); // 3. DUT becomes <AS> again. @@ -1271,7 +1299,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( 0x5000); @@ -1290,7 +1319,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); } @@ -1305,7 +1335,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isFalse(); } @@ -1492,7 +1523,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby( @@ -1510,7 +1542,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby( @@ -1525,7 +1558,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_STANDBY); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress, @@ -1543,7 +1577,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_UNKNOWN); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index b3f008598dc8..68803023c451 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -198,8 +198,8 @@ public class HdmiCecLocalDeviceTest { ADDR_PLAYBACK_1, Constants.MESSAGE_CEC_VERSION, HdmiCecMessage.EMPTY_PARAM); - boolean handleResult = mHdmiLocalDevice.dispatchMessage(msg); - assertFalse(handleResult); + @Constants.HandleMessageResult int handleResult = mHdmiLocalDevice.dispatchMessage(msg); + assertEquals(Constants.NOT_HANDLED, handleResult); } @Test @@ -213,7 +213,7 @@ public class HdmiCecLocalDeviceTest { (byte) (DEVICE_TV & 0xFF) }; callbackResult = -1; - boolean handleResult = + @Constants.HandleMessageResult int handleResult = mHdmiLocalDevice.handleGivePhysicalAddress( (int finalResult) -> callbackResult = finalResult); mTestLooper.dispatchAll(); @@ -221,7 +221,7 @@ public class HdmiCecLocalDeviceTest { * Test if CecMessage is sent successfully SendMessageResult#SUCCESS is defined in HAL as 0 */ assertEquals(0, callbackResult); - assertTrue(handleResult); + assertEquals(Constants.HANDLED, handleResult); } @Test @@ -251,85 +251,85 @@ public class HdmiCecLocalDeviceTest { public void handleUserControlPressed_volumeUp() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP)); - assertTrue(result); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_volumeDown() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); - assertTrue(result); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_volumeMute() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_MUTE)); - assertTrue(result); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_volumeUp_disabled() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP)); - assertFalse(result); + assertThat(result).isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleUserControlPressed_volumeDown_disabled() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); - assertFalse(result); + assertThat(result).isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleUserControlPressed_volumeMute_disabled() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_MUTE)); - assertFalse(result); + assertThat(result).isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleCecVersion_isHandled() { - boolean result = mHdmiLocalDevice.onMessage( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.onMessage( HdmiCecMessageBuilder.buildCecVersion(ADDR_PLAYBACK_1, mHdmiLocalDevice.mAddress, HdmiControlManager.HDMI_CEC_VERSION_1_4_B)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_power_localDeviceInStandby_shouldTurnOn() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isTrue(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -337,11 +337,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_power_localDeviceOn_shouldNotChangePowerStatus() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -349,11 +349,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerToggleFunction_localDeviceInStandby_shouldTurnOn() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isTrue(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -361,11 +361,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerToggleFunction_localDeviceOn_shouldTurnOff() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isTrue(); } @@ -373,11 +373,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOnFunction_localDeviceInStandby_shouldTurnOn() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isTrue(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -385,11 +385,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOnFunction_localDeviceOn_noPowerStatusChange() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -397,11 +397,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOffFunction_localDeviceStandby_noPowerStatusChange() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -409,11 +409,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOffFunction_localDeviceOn_shouldTurnOff() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isTrue(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 4b3ef2f2cfd1..39e06a3a362d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -233,7 +233,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1, mTvLogicalAddress); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); } @@ -247,7 +247,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); } @@ -261,7 +261,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1, mTvLogicalAddress); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); } @@ -275,7 +275,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index b5336e3fd807..68aa96a1be7f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -28,6 +28,9 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -68,7 +71,7 @@ import java.util.Optional; @RunWith(JUnit4.class) public class HdmiControlServiceTest { - private class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { + protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { private boolean mCanGoToStandby; private boolean mIsStandby; @@ -118,7 +121,7 @@ public class HdmiControlServiceTest { mCanGoToStandby = canGoToStandby; } } - private class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem { + protected static class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem { private boolean mCanGoToStandby; private boolean mIsStandby; @@ -171,15 +174,14 @@ public class HdmiControlServiceTest { private static final String TAG = "HdmiControlServiceTest"; private Context mContextSpy; - private HdmiControlService mHdmiControlService; + private HdmiControlService mHdmiControlServiceSpy; private HdmiCecController mHdmiCecController; - private MockAudioSystemDevice mAudioSystemDevice; - private MockPlaybackDevice mPlaybackDevice; + private MockAudioSystemDevice mAudioSystemDeviceSpy; + private MockPlaybackDevice mPlaybackDeviceSpy; private FakeNativeWrapper mNativeWrapper; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); - private boolean mStandbyMessageReceived; private HdmiPortInfo[] mHdmiPortInfo; @Mock private IPowerManager mIPowerManagerMock; @@ -199,36 +201,32 @@ public class HdmiControlServiceTest { HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - mHdmiControlService = new HdmiControlService(mContextSpy) { - @Override - boolean isStandbyMessageReceived() { - return mStandbyMessageReceived; - } + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy)); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); - @Override - protected void writeStringSystemProperty(String key, String value) { - } - }; mMyLooper = mTestLooper.getLooper(); - mAudioSystemDevice = new MockAudioSystemDevice(mHdmiControlService); - mPlaybackDevice = new MockPlaybackDevice(mHdmiControlService); - mAudioSystemDevice.init(); - mPlaybackDevice.init(); + mAudioSystemDeviceSpy = spy(new MockAudioSystemDevice(mHdmiControlServiceSpy)); + mPlaybackDeviceSpy = spy(new MockPlaybackDevice(mHdmiControlServiceSpy)); + mAudioSystemDeviceSpy.init(); + mPlaybackDeviceSpy.init(); - mHdmiControlService.setIoLooper(mMyLooper); - mHdmiControlService.setHdmiCecConfig(hdmiCecConfig); - mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); + mHdmiControlServiceSpy.setIoLooper(mMyLooper); + mHdmiControlServiceSpy.setHdmiCecConfig(hdmiCecConfig); + mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); - mHdmiControlService.setCecController(mHdmiCecController); - mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - - mLocalDevices.add(mAudioSystemDevice); - mLocalDevices.add(mPlaybackDevice); + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create( + mHdmiControlServiceSpy)); + mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator( + mHdmiControlServiceSpy)); + + mLocalDevices.add(mAudioSystemDeviceSpy); + mLocalDevices.add(mPlaybackDeviceSpy); mHdmiPortInfo = new HdmiPortInfo[4]; mHdmiPortInfo[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); @@ -239,80 +237,81 @@ public class HdmiControlServiceTest { mHdmiPortInfo[3] = new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.initService(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.initService(); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); } @Test public void onStandby_notByCec_cannotGoToStandby() { - mStandbyMessageReceived = false; - mPlaybackDevice.setCanGoToStandby(false); + doReturn(false).when(mHdmiControlServiceSpy).isStandbyMessageReceived(); + + mPlaybackDeviceSpy.setCanGoToStandby(false); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); - assertTrue(mPlaybackDevice.isStandby()); - assertTrue(mAudioSystemDevice.isStandby()); - assertFalse(mPlaybackDevice.isDisabled()); - assertFalse(mAudioSystemDevice.isDisabled()); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + assertTrue(mPlaybackDeviceSpy.isStandby()); + assertTrue(mAudioSystemDeviceSpy.isStandby()); + assertFalse(mPlaybackDeviceSpy.isDisabled()); + assertFalse(mAudioSystemDeviceSpy.isDisabled()); } @Test public void onStandby_byCec() { - mStandbyMessageReceived = true; + doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived(); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); - assertTrue(mPlaybackDevice.isStandby()); - assertTrue(mAudioSystemDevice.isStandby()); - assertTrue(mPlaybackDevice.isDisabled()); - assertTrue(mAudioSystemDevice.isDisabled()); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + assertTrue(mPlaybackDeviceSpy.isStandby()); + assertTrue(mAudioSystemDeviceSpy.isStandby()); + assertTrue(mPlaybackDeviceSpy.isDisabled()); + assertTrue(mAudioSystemDeviceSpy.isDisabled()); } @Test public void initialPowerStatus_normalBoot_isTransientToStandby() { - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); } @Test public void initialPowerStatus_quiescentBoot_isTransientToStandby() throws RemoteException { when(mIPowerManagerMock.isInteractive()).thenReturn(false); - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); } @Test public void powerStatusAfterBootComplete_normalBoot_isOn() { - mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); - mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED); - assertThat(mHdmiControlService.getPowerStatus()).isEqualTo( + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); + mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED); + assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); } @Test public void powerStatusAfterBootComplete_quiescentBoot_isStandby() throws RemoteException { when(mIPowerManagerMock.isInteractive()).thenReturn(false); - mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED); - assertThat(mHdmiControlService.getPowerStatus()).isEqualTo( + mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED); + assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_STANDBY); } @Test public void initialPowerStatus_normalBoot_goToStandby_doesNotBroadcastsPowerStatus_1_4() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); mNativeWrapper.clearResultMessages(); - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, @@ -322,21 +321,21 @@ public class HdmiControlServiceTest { @Test public void initialPowerStatus_normalBoot_goToStandby_broadcastsPowerStatus_2_0() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); mNativeWrapper.clearResultMessages(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( @@ -347,53 +346,53 @@ public class HdmiControlServiceTest { @Test public void setAndGetCecVolumeControlEnabled_isApi() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @Test public void setAndGetCecVolumeControlEnabled_changesSetting() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_DISABLED); - assertThat(mHdmiControlService.readIntSetting( + assertThat(mHdmiControlServiceSpy.readIntSetting( Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); - assertThat(mHdmiControlService.readIntSetting( + assertThat(mHdmiControlServiceSpy.readIntSetting( Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @Test public void setAndGetCecVolumeControlEnabledInternal_doesNotChangeSetting() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @@ -401,60 +400,61 @@ public class HdmiControlServiceTest { @Test public void disableAndReenableCec_volumeControlReturnsToOriginalValue_enabled() { int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED; - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled); + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(volumeControlEnabled); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()) + .isEqualTo(volumeControlEnabled); } @Test public void disableAndReenableCec_volumeControlReturnsToOriginalValue_disabled() { int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_DISABLED; - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, volumeControlEnabled); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( volumeControlEnabled); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( volumeControlEnabled); } @Test public void disableAndReenableCec_volumeControlFeatureListenersNotified() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); assertThat(callback.mCallbackReceived).isTrue(); assertThat(callback.mVolumeControlEnabled).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); assertThat(callback.mVolumeControlEnabled).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @Test public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); mTestLooper.dispatchAll(); assertThat(callback.mCallbackReceived).isTrue(); @@ -464,11 +464,11 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); mTestLooper.dispatchAll(); assertThat(callback.mCallbackReceived).isTrue(); @@ -478,13 +478,13 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); mTestLooper.dispatchAll(); @@ -495,15 +495,15 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); mTestLooper.dispatchAll(); - mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.removeHdmiControlVolumeControlStatusChangeListener(callback); + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); mTestLooper.dispatchAll(); @@ -514,16 +514,16 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback(); VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback1); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback1); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback2); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); mTestLooper.dispatchAll(); @@ -537,47 +537,48 @@ public class HdmiControlServiceTest { @Test public void getCecVersion_1_4() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } @Test public void getCecVersion_2_0() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_2_0); } @Test public void getCecVersion_change() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_2_0); } @Test public void handleGiveFeatures_cec14_featureAbort() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV, @@ -592,11 +593,11 @@ public class HdmiControlServiceTest { @Test public void handleGiveFeatures_cec20_reportsFeatures() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV, @@ -606,43 +607,43 @@ public class HdmiControlServiceTest { HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), - mPlaybackDevice.getDeviceFeatures()); + mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), + mPlaybackDeviceSpy.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures); } @Test public void initializeCec_14_doesNotBroadcastReportFeatures() { mNativeWrapper.clearResultMessages(); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), - mPlaybackDevice.getDeviceFeatures()); + mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), + mPlaybackDeviceSpy.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportFeatures); } @Test public void initializeCec_20_reportsFeaturesBroadcast() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), - mPlaybackDevice.getDeviceFeatures()); + mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), + mPlaybackDeviceSpy.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures); } @@ -653,7 +654,7 @@ public class HdmiControlServiceTest { Binder.setCallingWorkSourceUid(callerUid); WorkSourceUidReadingRunnable uidReadingRunnable = new WorkSourceUidReadingRunnable(); - mHdmiControlService.runOnServiceThread(uidReadingRunnable); + mHdmiControlServiceSpy.runOnServiceThread(uidReadingRunnable); Binder.setCallingWorkSourceUid(runnerUid); @@ -666,36 +667,36 @@ public class HdmiControlServiceTest { @Test public void initCecVersion_limitToMinimumSupportedVersion() { mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } @Test public void initCecVersion_limitToAtLeast1_4() { mNativeWrapper.setCecVersion(0x0); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } @Test public void initCecVersion_useHighestMatchingVersion() { mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_2_0); } @@ -710,4 +711,140 @@ public class HdmiControlServiceTest { this.mVolumeControlEnabled = enabled; } } + + @Test + public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() { + // Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("40:8D:03"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.ABORT_INVALID_OPERAND); + } + + @Test + public void handleCecCommand_errorSource_returnsHandled() { + // Validity ERROR_SOURCE. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("F0:8E"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + + } + + @Test + public void handleCecCommand_errorDestination_returnsHandled() { + // Validity ERROR_DESTINATION. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("0F:8E:00"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void handleCecCommand_errorParameterShort_returnsHandled() { + // Validity ERROR_PARAMETER_SHORT + // Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("40:8E"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void handleCecCommand_notHandledByLocalDevice_returnsNotHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + + doReturn(Constants.NOT_HANDLED).when(mHdmiControlServiceSpy) + .dispatchMessageToLocalDevice(message); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.NOT_HANDLED); + } + + @Test + public void handleCecCommand_handledByLocalDevice_returnsHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + + doReturn(Constants.HANDLED).when(mHdmiControlServiceSpy) + .dispatchMessageToLocalDevice(message); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void handleCecCommand_localDeviceReturnsFeatureAbort_returnsFeatureAbort() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + + doReturn(Constants.ABORT_REFUSED).when(mHdmiControlServiceSpy) + .dispatchMessageToLocalDevice(message); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.ABORT_REFUSED); + } + + @Test + public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_BROADCAST); + + doReturn(Constants.ABORT_REFUSED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.ABORT_NOT_IN_CORRECT_MODE) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void dispatchMessageToLocalDevice_localDevicesDoNotHandleMessage_returnsUnhandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1); + + doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.NOT_HANDLED) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.NOT_HANDLED); + } + + @Test + public void dispatchMessageToLocalDevice_localDeviceHandlesMessage_returnsHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1); + + doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.HANDLED) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void dispatchMessageToLocalDevice_localDeviceReturnsFeatureAbort_returnsFeatureAbort() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1); + + doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.ABORT_REFUSED) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.ABORT_REFUSED); + } } diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java index a7b32ac5c387..68a6e6017bc6 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 @@ public class BackgroundRestrictionsTest { 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 3ea86f2e9ac0..87881bf839cc 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 @@ public class MockPriorityJobService extends JobService { 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/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index 91342ce925f6..8c08226201a8 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -21,6 +21,7 @@ import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_PROFILE; import static android.os.UserHandle.USER_SYSTEM; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -110,6 +111,10 @@ public class RebootEscrowManagerTests { public interface MockableRebootEscrowInjected { int getBootCount(); + long getCurrentTimeMillis(); + + boolean forceServerBased(); + void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); } @@ -174,6 +179,9 @@ public class RebootEscrowManagerTests { @Override public boolean serverBasedResumeOnReboot() { + if (mInjected.forceServerBased()) { + return true; + } return mServerBased; } @@ -205,9 +213,20 @@ public class RebootEscrowManagerTests { } @Override + public String getVbmetaDigest(boolean other) { + return other ? "" : "fake digest"; + } + + @Override + public long getCurrentTimeMillis() { + return mInjected.getCurrentTimeMillis(); + } + + @Override public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete) { + mInjected.reportMetric(success, errorCode, serviceType, attemptCount, escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete); } @@ -430,16 +449,21 @@ public class RebootEscrowManagerTests { // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); + when(mInjected.getCurrentTimeMillis()).thenReturn(30000L); + mStorage.setLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, 10000L, + USER_SYSTEM); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */, - anyInt(), anyInt(), anyInt()); + eq(20), eq(0) /* vbmeta status */, anyInt()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); + assertEquals(mStorage.getLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, + -1, USER_SYSTEM), -1); } @Test @@ -468,7 +492,7 @@ public class RebootEscrowManagerTests { ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */, - anyInt(), anyInt(), anyInt()); + anyInt(), eq(0) /* vbmeta status */, anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -479,6 +503,84 @@ public class RebootEscrowManagerTests { } @Test + public void loadRebootEscrowDataIfAvailable_ServerBasedRemoteException_Failure() + throws Exception { + setServerBasedRebootEscrowProvider(); + + when(mInjected.getBootCount()).thenReturn(0); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + // Use x -> x for both wrap & unwrap functions. + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + metricsErrorCodeCaptor.capture(), eq(2) /* Server based */, + eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); + + when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class); + mService.loadRebootEscrowDataIfAvailable(null); + verify(mServiceConnection).unwrap(any(), anyLong()); + assertFalse(metricsSuccessCaptor.getValue()); + assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY), + metricsErrorCodeCaptor.getValue()); + } + + @Test + public void loadRebootEscrowDataIfAvailable_ServerBasedIoError_RetryFailure() throws Exception { + setServerBasedRebootEscrowProvider(); + + when(mInjected.getBootCount()).thenReturn(0); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + // Use x -> x for both wrap & unwrap functions. + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + metricsErrorCodeCaptor.capture(), eq(2) /* Server based */, + eq(2) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); + when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(IOException.class); + + HandlerThread thread = new HandlerThread("RebootEscrowManagerTest"); + thread.start(); + mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper())); + // Sleep 5s for the retry to complete + Thread.sleep(5 * 1000); + assertFalse(metricsSuccessCaptor.getValue()); + assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_RETRY_COUNT_EXHAUSTED), + metricsErrorCodeCaptor.getValue()); + } + + @Test public void loadRebootEscrowDataIfAvailable_ServerBased_RetrySuccess() throws Exception { setServerBasedRebootEscrowProvider(); @@ -607,9 +709,14 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); + // Trigger a vbmeta digest mismatch + mStorage.setString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST, + "non sense value", USER_SYSTEM); mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected).reportMetric(eq(true), eq(0) /* error code */, eq(1) /* HAL based */, - eq(1) /* attempt count */, anyInt(), anyInt(), anyInt()); + eq(1) /* attempt count */, anyInt(), eq(2) /* vbmeta status */, anyInt()); + assertEquals(mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST, + "", USER_SYSTEM), ""); } @Test @@ -636,12 +743,17 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); + // Return a null escrow key doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), - anyInt() /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */, - anyInt(), anyInt(), anyInt()); - when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); + metricsErrorCodeCaptor.capture(), eq(1) /* HAL based */, + eq(1) /* attempt count */, anyInt(), anyInt(), anyInt()); + + when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> null); mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertFalse(metricsSuccessCaptor.getValue()); + assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY), + metricsErrorCodeCaptor.getValue()); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 029e9a39ea4b..1ab70e524d3c 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.admin.DevicePolicyManager; 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { @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 abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { shutdownServices(); + mMockAppSearchManager.removeShortcuts(); + mMockAppSearchManager = null; + super.tearDown(); } @@ -1891,6 +1995,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 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 4d0beef99cca..3f680e6c8d22 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.waitForBitmapSavesForTest(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { + Log.d("ShortcutManagerTest1", si.toString()); assertTrue(si.hasIconFile()); }); @@ -1702,8 +1703,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 395b643e3777..fc2661103491 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.ExecutorService; 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 @@ public final class UserManagerTest { fail("Didn't find a guest: " + list); } + @Test + public void testCloneUser() throws Exception { + // Test that only one clone user can be created + final int primaryUserId = mUserManager.getPrimaryUser().id; + UserInfo userInfo = createProfileForUser("Clone user1", + UserManager.USER_TYPE_PROFILE_CLONE, + primaryUserId); + assertThat(userInfo).isNotNull(); + UserInfo userInfo2 = createProfileForUser("Clone user2", + UserManager.USER_TYPE_PROFILE_CLONE, + primaryUserId); + assertThat(userInfo2).isNull(); + + final Context userContext = mContext.createPackageContextAsUser("system", 0, + UserHandle.of(userInfo.id)); + assertThat(userContext.getSystemService( + UserManager.class).sharesMediaWithParent()).isTrue(); + + List<UserInfo> list = mUserManager.getUsers(); + List<UserInfo> cloneUsers = list.stream().filter( + user -> (user.id == userInfo.id && user.name.equals("Clone user1") + && user.isCloneProfile())) + .collect(Collectors.toList()); + assertThat(cloneUsers.size()).isEqualTo(1); + + // Verify clone user parent + assertThat(mUserManager.getProfileParent(primaryUserId)).isNull(); + UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id); + assertThat(parentProfileInfo).isNotNull(); + assertThat(primaryUserId).isEqualTo(parentProfileInfo.id); + removeUser(userInfo.id); + assertThat(mUserManager.getProfileParent(primaryUserId)).isNull(); + } + @MediumTest @Test public void testAdd2Users() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java index 324e5929f77f..7903a90979fb 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java @@ -22,6 +22,7 @@ import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -72,6 +73,7 @@ public class RecoverySystemServiceTest { private LockSettingsInternal mLockSettingsInternal; private IBootControl mIBootControl; private RecoverySystemServiceTestable.IMetricsReporter mMetricsReporter; + private RecoverySystemService.PreferencesManager mSharedPreferences; private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package"; private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package"; @@ -97,10 +99,11 @@ public class RecoverySystemServiceTest { when(mIBootControl.getActiveBootSlot()).thenReturn(1); mMetricsReporter = mock(RecoverySystemServiceTestable.IMetricsReporter.class); + mSharedPreferences = mock(RecoverySystemService.PreferencesManager.class); mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties, powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal, - mIBootControl, mMetricsReporter); + mIBootControl, mMetricsReporter, mSharedPreferences); } @Test @@ -237,6 +240,8 @@ public class RecoverySystemServiceTest { is(true)); verify(mMetricsReporter).reportRebootEscrowPreparationMetrics( eq(1000), eq(0) /* need preparation */, eq(1) /* client count */); + verify(mSharedPreferences).putLong(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), eq(100_000L)); } @@ -245,10 +250,19 @@ public class RecoverySystemServiceTest { IntentSender intentSender = mock(IntentSender.class); assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender), is(true)); + + when(mSharedPreferences.getLong(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), anyLong())) + .thenReturn(200_000L).thenReturn(5000L); + mRecoverySystemService.onPreparedForReboot(true); + verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics( + eq(1000), eq(1) /* client count */, + eq(-1) /* invalid duration */); + mRecoverySystemService.onPreparedForReboot(true); verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any()); verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics( - eq(1000), eq(1) /* client count */, anyInt() /* duration */); + eq(1000), eq(1) /* client count */, eq(95) /* duration */); } @Test @@ -352,12 +366,19 @@ public class RecoverySystemServiceTest { public void rebootWithLskf_Success() throws Exception { assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true)); mRecoverySystemService.onPreparedForReboot(true); + + when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2); + when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF), + anyInt())).thenReturn(3); + when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF), + anyLong())).thenReturn(40_000L); assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true), is(true)); verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000), - eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(60) /* duration */, eq(3) /* lskf capture count */); } @@ -400,13 +421,19 @@ public class RecoverySystemServiceTest { assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true)); mRecoverySystemService.onPreparedForReboot(true); - // Client B's clear won't affect client A's preparation. + when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2); + when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF), + anyInt())).thenReturn(1); + when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF), + anyLong())).thenReturn(60_000L); + assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true), is(true)); verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000), - eq(2) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + eq(2) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(40), eq(1) /* lskf capture count */); } @Test @@ -415,22 +442,30 @@ public class RecoverySystemServiceTest { mRecoverySystemService.onPreparedForReboot(true); assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true)); + when(mSharedPreferences.getInt(eq(FAKE_OTHER_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2); + when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF), + anyInt())).thenReturn(1); + when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF), + anyLong())).thenReturn(60_000L); + assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true)); assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true), is(false)); verifyNoMoreInteractions(mIPowerManager); verify(mMetricsReporter).reportRebootEscrowRebootMetrics(not(eq(0)), eq(1000), - eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + eq(1) /* client count */, anyInt() /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(40), eq(1)/* lskf capture count */); assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true)); assertThat( mRecoverySystemService.rebootWithLskf(FAKE_OTHER_PACKAGE_NAME, "ab-update", true), is(true)); verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); - verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(2000), - eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + + verify(mMetricsReporter).reportRebootEscrowRebootMetrics((eq(0)), eq(2000), + eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(40), eq(1) /* lskf capture count */); } @Test diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java index a894178fca06..27e953f30fa0 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java @@ -33,11 +33,13 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { private final LockSettingsInternal mLockSettingsInternal; private final IBootControl mIBootControl; private final IMetricsReporter mIMetricsReporter; + private final RecoverySystemService.PreferencesManager mSharedPreferences; MockInjector(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal, - IBootControl bootControl, IMetricsReporter metricsReporter) { + IBootControl bootControl, IMetricsReporter metricsReporter, + RecoverySystemService.PreferencesManager preferences) { super(context); mSystemProperties = systemProperties; mPowerManager = powerManager; @@ -46,6 +48,7 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { mLockSettingsInternal = lockSettingsInternal; mIBootControl = bootControl; mIMetricsReporter = metricsReporter; + mSharedPreferences = preferences; } @Override @@ -114,12 +117,14 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { requestedClientCount); } + @Override public void reportRebootEscrowLskfCapturedMetrics(int uid, int requestedClientCount, int requestedToLskfCapturedDurationInSeconds) { mIMetricsReporter.reportRebootEscrowLskfCapturedMetrics(uid, requestedClientCount, requestedToLskfCapturedDurationInSeconds); } + @Override public void reportRebootEscrowRebootMetrics(int errorCode, int uid, int preparedClientCount, int requestCount, boolean slotSwitch, boolean serverBased, int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts) { @@ -127,14 +132,25 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { requestCount, slotSwitch, serverBased, lskfCapturedToRebootDurationInSeconds, lskfCapturedCounts); } + + @Override + public long getCurrentTimeMillis() { + return 100_000; + } + + @Override + public RecoverySystemService.PreferencesManager getMetricsPrefs() { + return mSharedPreferences; + } } RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal, - IBootControl bootControl, IMetricsReporter metricsReporter) { + IBootControl bootControl, IMetricsReporter metricsReporter, + RecoverySystemService.PreferencesManager preferences) { super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter, - uncryptSocket, lockSettingsInternal, bootControl, metricsReporter)); + uncryptSocket, lockSettingsInternal, bootControl, metricsReporter, preferences)); } public static class FakeSystemProperties { @@ -176,5 +192,4 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { int requestCount, boolean slotSwitch, boolean serverBased, int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts); } - } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 106827078290..742f5034a248 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -217,20 +217,20 @@ public class TimeDetectorServiceTest { fail(); } finally { verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SET_TIME), anyString()); + eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString()); } } @Test public void testSuggestExternalTime() throws Exception { - doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion(); mTimeDetectorService.suggestExternalTime(externalTimeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SET_TIME), anyString()); + eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString()); mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestExternalTimeCalled(externalTimeSuggestion); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java index 5a100a297cfc..a0e9d977954f 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 @@ public class ControllerImplTest { private TestCallback mTestCallback; private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider; private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider; - private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker; @Before public void setUp() { @@ -84,13 +83,10 @@ public class ControllerImplTest { }; 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 @@ public class ControllerImplTest { * 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 @@ public class ControllerImplTest { 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 000000000000..e75d05c9a686 --- /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 d13a04e13406..0edb559b04b3 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 org.junit.Test; 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 @@ public class LocationTimeZoneProviderTest { 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 @@ public class LocationTimeZoneProviderTest { 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 @@ public class LocationTimeZoneProviderTest { 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 @@ public class LocationTimeZoneProviderTest { 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 @@ public class LocationTimeZoneProviderTest { } @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 @@ public class LocationTimeZoneProviderTest { 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 @@ public class LocationTimeZoneProviderTest { } } - 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/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java index 5561b2c6a7aa..173705be4bf1 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java @@ -19,6 +19,7 @@ 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; @@ -26,29 +27,44 @@ import java.util.Arrays; import java.util.List; import java.util.TimeZone; +/** Tests for {@link ZoneInfoDbTimeZoneProviderEventPreProcessor}. */ @Presubmit -public class ZoneInfoDbTimeZoneIdValidatorTest { - private final LocationTimeZoneProvider.TimeZoneIdValidator mTzChecker = - new ZoneInfoDbTimeZoneIdValidator(); +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(mTzChecker.isValid(timeZone)).isTrue(); + .that(mPreProcessor.preProcess(event)).isEqualTo(event); } } @Test - public void nonExistingZones_areNotSupported() { + public void eventWithNonExistingZones_areMappedToUncertainEvent() { List<String> nonExistingTimeZones = Arrays.asList( - "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30" - ); + "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(mTzChecker.isValid(timeZone)) - .isFalse(); + .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 b54b6969e7df..1e3c34474820 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.annotation.Nullable; 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 @@ final class FakeVibratorControllerProvider { 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 @@ final class FakeVibratorControllerProvider { public OnVibrationCompleteListener listener; public boolean isInitialized; + @Override public void init(int vibratorId, OnVibrationCompleteListener listener) { isInitialized = true; this.vibratorId = vibratorId; this.listener = listener; } + @Override public boolean isAvailable() { return mIsAvailable; } + @Override public void on(long milliseconds, long vibrationId) { - VibrationEffect effect = VibrationEffect.createOneShot( - milliseconds, VibrationEffect.DEFAULT_AMPLITUDE); - mEffects.add(effect); + mEffectSegments.add(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, + /* frequency= */ 0, (int) milliseconds)); applyLatency(); scheduleListener(milliseconds, vibrationId); } + @Override public void off() { } - public void setAmplitude(int amplitude) { + @Override + public void setAmplitude(float amplitude) { mAmplitudes.add(amplitude); applyLatency(); } + @Override public int[] getSupportedEffects() { return mSupportedEffects; } + @Override public int[] getSupportedPrimitives() { return mSupportedPrimitives; } + @Override public float getResonantFrequency() { return mResonantFrequency; } + @Override public float getQFactor() { return mQFactor; } + @Override public long perform(long effect, long strength, long vibrationId) { if (mSupportedEffects == null || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) { return 0; } - mEffects.add(new VibrationEffect.Prebaked((int) effect, false, (int) strength)); + mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength)); applyLatency(); scheduleListener(EFFECT_DURATION, vibrationId); return EFFECT_DURATION; } - public long compose(VibrationEffect.Composition.PrimitiveEffect[] effect, - long vibrationId) { - VibrationEffect.Composed composed = new VibrationEffect.Composed(Arrays.asList(effect)); - mEffects.add(composed); - applyLatency(); + @Override + public long compose(PrimitiveSegment[] effects, long vibrationId) { long duration = 0; - for (VibrationEffect.Composition.PrimitiveEffect e : effect) { - duration += EFFECT_DURATION + e.delay; + for (PrimitiveSegment primitive : effects) { + duration += EFFECT_DURATION + primitive.getDelay(); + mEffectSegments.add(primitive); } + applyLatency(); scheduleListener(duration, vibrationId); return duration; } + @Override public void setExternalControl(boolean enabled) { } + @Override public long getCapabilities() { return mCapabilities; } + @Override public void alwaysOnEnable(long id, long effect, long strength) { - VibrationEffect.Prebaked prebaked = new VibrationEffect.Prebaked((int) effect, false, - (int) strength); + PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength); mEnabledAlwaysOnEffects.put(id, prebaked); } + @Override public void alwaysOnDisable(long id) { mEnabledAlwaysOnEffects.remove(id); } @@ -222,21 +237,21 @@ final class FakeVibratorControllerProvider { * 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 b6c11fe62ff6..59c0b0e96fcd 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 static org.mockito.Mockito.when; 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.VibrationAttributes; 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 @@ public class VibrationScalerTest { } @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); - - 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); + PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK, + /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); - VibrationEffect.Prebaked scaled = mVibrationScaler.scale( + PrebakedSegment scaled = mVibrationScaler.scale( effect, VibrationAttributes.USAGE_NOTIFICATION); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); @@ -196,25 +161,33 @@ public class VibrationScalerTest { } @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.Prebaked scaled = mVibrationScaler.scale( - effect, VibrationAttributes.USAGE_NOTIFICATION); - VibrationEffect.Prebaked scaledFallback1 = - (VibrationEffect.Prebaked) scaled.getFallbackEffect(); - VibrationEffect.OneShot scaledFallback2 = - (VibrationEffect.OneShot) scaledFallback1.getFallbackEffect(); + VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); + + PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + + 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); - assertEquals(scaledFallback1.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - assertTrue(scaledFallback2.getAmplitude() > 0); } @Test @@ -224,20 +197,20 @@ public class VibrationScalerTest { 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 @@ public class VibrationScalerTest { 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 @@ public class VibrationScalerTest { 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 7d5eec0834a1..37e0ec2159d9 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.SystemClock; 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.HashMap; 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { return array; } - private VibrationEffect expectedOneShot(long millis) { - return VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE); + private VibrationEffectSegment expectedOneShot(long millis) { + return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, /* frequency= */ 0, (int) millis); + } + + private 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 VibrationEffect expectedPrebaked(int effectId) { - return new VibrationEffect.Prebaked(effectId, false, - VibrationEffect.EFFECT_STRENGTH_MEDIUM); + 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 bad3e4c2ed92..0ba3a21b96ea 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.IBinder; 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 com.android.internal.util.test.FakeSettingsProviderRule; 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 @@ public class VibratorControllerTest { @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 @@ public class VibratorControllerTest { @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 @@ public class VibratorControllerTest { 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 @@ public class VibratorControllerTest { 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 @@ public class VibratorControllerTest { 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 ce6639c6b4aa..12ced388d5f7 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.VibrationEffect; 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { 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 @@ public class VibratorManagerServiceTest { .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 @@ public class VibratorManagerServiceTest { .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 @@ public class VibratorManagerServiceTest { .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 @@ public class VibratorManagerServiceTest { .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 @@ public class VibratorManagerServiceTest { vibrate(service, CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .combine(), ALARM_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, service, TEST_TIMEOUT_MILLIS)); vibrate(service, CombinedVibrationEffect.startSequential() .addNext(1, VibrationEffect.createOneShot(20, 100)) .combine(), NOTIFICATION_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(), HAPTIC_FEEDBACK_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 4, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); - assertEquals(3, fakeVibrator.getEffects().size()); + assertEquals(4, fakeVibrator.getEffectSegments().size()); assertEquals(1, fakeVibrator.getAmplitudes().size()); // Alarm vibration is always VIBRATION_INTENSITY_HIGH. - VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false, - VibrationEffect.EFFECT_STRENGTH_STRONG); - assertEquals(expected, fakeVibrator.getEffects().get(0)); + PrebakedSegment expected = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); + assertEquals(expected, fakeVibrator.getEffectSegments().get(0)); // Notification vibrations will be scaled with SCALE_VERY_HIGH. - assertTrue(150 < fakeVibrator.getAmplitudes().get(0)); + assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0)); // Haptic feedback vibrations will be scaled with SCALE_LOW. - VibrationEffect.Composed played = - (VibrationEffect.Composed) fakeVibrator.getEffects().get(2); - assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale); - assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale); + assertTrue(0.5 < ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale()); + assertTrue(0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(3)).getScale()); // Ring vibrations have intensity OFF and are not played. } diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS new file mode 100644 index 000000000000..d825dfd7cf00 --- /dev/null +++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pm/OWNERS diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 5462f47e3a4c..ff881748cfea 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { @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 b9ffd65ee20e..a37d5c8a956a 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.app.NotificationManager; 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 @@ public class NotificationComparatorTest extends UiServiceTestCase { 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 @@ public class NotificationComparatorTest extends UiServiceTestCase { 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 @@ public class NotificationComparatorTest extends UiServiceTestCase { .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 @@ public class NotificationComparatorTest extends UiServiceTestCase { 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 32e52003f471..adf8fa461c06 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_ATTACHED_ 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { 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/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 925b6f9601be..6ffdb099695d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -219,7 +219,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testAddTasksNoMultiple_expectNoTrim() { + public void testAddDocumentTasksNoMultiple_expectNoTrim() { // Add same non-multiple-task document tasks will remove the task (to re-add it) but not // trim it Task documentTask1 = createDocumentTask(".DocumentTask1"); @@ -262,7 +262,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testAddTasksMultipleDocumentTasks_expectNoTrim() { + public void testAddMultipleDocumentTasks_expectNoTrim() { // Add same multiple-task document tasks does not trim the first tasks Task documentTask1 = createDocumentTask(".DocumentTask1", FLAG_ACTIVITY_MULTIPLE_TASK); @@ -278,14 +278,14 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testAddTasksMultipleTasks_expectRemovedNoTrim() { - // Add multiple same-affinity non-document tasks, ensure that it removes the other task, - // but that it does not trim it + public void testAddTasks_expectRemovedNoTrim() { + // Add multiple same-affinity non-document tasks, ensure that it removes, but does not trim + // the other task Task task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .setFlags(FLAG_ACTIVITY_NEW_TASK) .build(); Task task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .setFlags(FLAG_ACTIVITY_NEW_TASK) .build(); mRecentTasks.add(task1); assertThat(mCallbacksRecorder.mAdded).hasSize(1); @@ -302,6 +302,29 @@ public class RecentTasksTest extends WindowTestsBase { } @Test + public void testAddMultipleTasks_expectNotRemoved() { + // Add multiple same-affinity non-document tasks with MULTIPLE_TASK, ensure that it does not + // remove the other task + Task task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .build(); + Task task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .build(); + mRecentTasks.add(task1); + assertThat(mCallbacksRecorder.mAdded).hasSize(1); + assertThat(mCallbacksRecorder.mAdded).contains(task1); + assertThat(mCallbacksRecorder.mTrimmed).isEmpty(); + assertThat(mCallbacksRecorder.mRemoved).isEmpty(); + mCallbacksRecorder.clear(); + mRecentTasks.add(task2); + assertThat(mCallbacksRecorder.mAdded).hasSize(1); + assertThat(mCallbacksRecorder.mAdded).contains(task2); + assertThat(mCallbacksRecorder.mTrimmed).isEmpty(); + assertThat(mCallbacksRecorder.mRemoved).isEmpty(); + } + + @Test public void testAddTasksDifferentStacks_expectNoRemove() { // Adding the same task with different activity types should not trigger removal of the // other task 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 7a4ad7410163..f97e79444d59 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 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } @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 956c277e18c1..37da529d43b7 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 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } @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 0c7e61765501..82cb728f5bd2 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.annotation.NonNull; 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 @@ final class RemoteTranslationService extends ServiceConnector.Impl<ITranslationS 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 72e1e33491ff..6203ae954065 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.os.ShellCallback; 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 @@ public final class TranslationManagerService } 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 @@ public final class TranslationManagerService @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 @@ public final class TranslationManagerService 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 @@ public final class TranslationManagerService @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 1ca07cb8d928..ee5ec47ec71a 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.IBinder; 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 @@ final class TranslationManagerServiceImpl extends } @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 @@ final class TranslationManagerServiceImpl extends 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 @@ final class TranslationManagerServiceImpl extends + "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 4ae11b8458cb..23cf5116b2da 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -447,6 +447,9 @@ public class Annotation { 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/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java index 30480d14e453..a3aaf61a6fec 100644 --- a/telephony/java/android/telephony/PhoneCapability.java +++ b/telephony/java/android/telephony/PhoneCapability.java @@ -26,6 +26,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -47,26 +48,18 @@ public final class PhoneCapability implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "DEVICE_NR_CAPABILITY_" }, value = { - DEVICE_NR_CAPABILITY_NONE, DEVICE_NR_CAPABILITY_NSA, DEVICE_NR_CAPABILITY_SA, }) public @interface DeviceNrCapability {} /** - * Indicates DEVICE_NR_CAPABILITY_NONE determine that the device does not enable 5G NR. - * @hide - */ - @SystemApi - public static final int DEVICE_NR_CAPABILITY_NONE = 0; - - /** * Indicates DEVICE_NR_CAPABILITY_NSA determine that the device enable the non-standalone * (NSA) mode of 5G NR. * @hide */ @SystemApi - public static final int DEVICE_NR_CAPABILITY_NSA = 1 << 0; + public static final int DEVICE_NR_CAPABILITY_NSA = 1; /** * Indicates DEVICE_NR_CAPABILITY_SA determine that the device enable the standalone (SA) @@ -74,7 +67,7 @@ public final class PhoneCapability implements Parcelable { * @hide */ @SystemApi - public static final int DEVICE_NR_CAPABILITY_SA = 1 << 1; + public static final int DEVICE_NR_CAPABILITY_SA = 2; static { ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true); @@ -83,31 +76,34 @@ public final class PhoneCapability implements Parcelable { List<ModemInfo> logicalModemList = new ArrayList<>(); logicalModemList.add(modemInfo1); logicalModemList.add(modemInfo2); + int[] deviceNrCapabilities = new int[0]; + DEFAULT_DSDS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false, - DEVICE_NR_CAPABILITY_NONE); + deviceNrCapabilities); logicalModemList = new ArrayList<>(); logicalModemList.add(modemInfo1); DEFAULT_SSSS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false, - DEVICE_NR_CAPABILITY_NONE); + deviceNrCapabilities); } /** - * MaxActivePsVoice defines the maximum number of active voice calls. For a dual sim dual - * standby (DSDS) modem it would be one, but for a dual sim dual active modem it would be 2. + * mMaxActiveVoiceSubscriptions defines the maximum subscriptions that can support + * simultaneous voice calls. For a dual sim dual standby (DSDS) device it would be one, but + * for a dual sim dual active device it would be 2. * * @hide */ - private final int mMaxActivePsVoice; + private final int mMaxActiveVoiceSubscriptions; /** - * MaxActiveInternetData defines how many logical modems can have - * PS attached simultaneously. For example, for L+L modem it - * should be 2. + * mMaxActiveDataSubscriptions defines the maximum subscriptions that can support + * simultaneous data connections. + * For example, for L+L device it should be 2. * * @hide */ - private final int mMaxActiveInternetData; + private final int mMaxActiveDataSubscriptions; /** * Whether modem supports both internet PDN up so @@ -126,42 +122,45 @@ public final class PhoneCapability implements Parcelable { * * @hide */ - private final int mDeviceNrCapability; + private final int[] mDeviceNrCapabilities; /** @hide */ - public PhoneCapability(int maxActivePsVoice, int maxActiveInternetData, + public PhoneCapability(int maxActiveVoiceSubscriptions, int maxActiveDataSubscriptions, List<ModemInfo> logicalModemList, boolean networkValidationBeforeSwitchSupported, - int deviceNrCapability) { - this.mMaxActivePsVoice = maxActivePsVoice; - this.mMaxActiveInternetData = maxActiveInternetData; + int[] deviceNrCapabilities) { + this.mMaxActiveVoiceSubscriptions = maxActiveVoiceSubscriptions; + this.mMaxActiveDataSubscriptions = maxActiveDataSubscriptions; // Make sure it's not null. this.mLogicalModemList = logicalModemList == null ? new ArrayList<>() : logicalModemList; this.mNetworkValidationBeforeSwitchSupported = networkValidationBeforeSwitchSupported; - this.mDeviceNrCapability = deviceNrCapability; + this.mDeviceNrCapabilities = deviceNrCapabilities; } @Override public String toString() { - return "mMaxActivePsVoice=" + mMaxActivePsVoice - + " mMaxActiveInternetData=" + mMaxActiveInternetData + return "mMaxActiveVoiceSubscriptions=" + mMaxActiveVoiceSubscriptions + + " mMaxActiveDataSubscriptions=" + mMaxActiveDataSubscriptions + " mNetworkValidationBeforeSwitchSupported=" + mNetworkValidationBeforeSwitchSupported - + " mDeviceNrCapability " + mDeviceNrCapability; + + " mDeviceNrCapability " + Arrays.toString(mDeviceNrCapabilities); } private PhoneCapability(Parcel in) { - mMaxActivePsVoice = in.readInt(); - mMaxActiveInternetData = in.readInt(); + mMaxActiveVoiceSubscriptions = in.readInt(); + mMaxActiveDataSubscriptions = in.readInt(); mNetworkValidationBeforeSwitchSupported = in.readBoolean(); mLogicalModemList = new ArrayList<>(); in.readList(mLogicalModemList, ModemInfo.class.getClassLoader()); - mDeviceNrCapability = in.readInt(); + mDeviceNrCapabilities = in.createIntArray(); } @Override public int hashCode() { - return Objects.hash(mMaxActivePsVoice, mMaxActiveInternetData, mLogicalModemList, - mNetworkValidationBeforeSwitchSupported, mDeviceNrCapability); + return Objects.hash(mMaxActiveVoiceSubscriptions, + mMaxActiveDataSubscriptions, + mLogicalModemList, + mNetworkValidationBeforeSwitchSupported, + Arrays.hashCode(mDeviceNrCapabilities)); } @Override @@ -176,12 +175,12 @@ public final class PhoneCapability implements Parcelable { PhoneCapability s = (PhoneCapability) o; - return (mMaxActivePsVoice == s.mMaxActivePsVoice - && mMaxActiveInternetData == s.mMaxActiveInternetData + return (mMaxActiveVoiceSubscriptions == s.mMaxActiveVoiceSubscriptions + && mMaxActiveDataSubscriptions == s.mMaxActiveDataSubscriptions && mNetworkValidationBeforeSwitchSupported == s.mNetworkValidationBeforeSwitchSupported && mLogicalModemList.equals(s.mLogicalModemList) - && mDeviceNrCapability == s.mDeviceNrCapability); + && Arrays.equals(mDeviceNrCapabilities, s.mDeviceNrCapabilities)); } /** @@ -195,11 +194,11 @@ public final class PhoneCapability implements Parcelable { * {@link Parcelable#writeToParcel} */ public void writeToParcel(@NonNull Parcel dest, @Parcelable.WriteFlags int flags) { - dest.writeInt(mMaxActivePsVoice); - dest.writeInt(mMaxActiveInternetData); + dest.writeInt(mMaxActiveVoiceSubscriptions); + dest.writeInt(mMaxActiveDataSubscriptions); dest.writeBoolean(mNetworkValidationBeforeSwitchSupported); dest.writeList(mLogicalModemList); - dest.writeInt(mDeviceNrCapability); + dest.writeIntArray(mDeviceNrCapabilities); } public static final @android.annotation.NonNull Parcelable.Creator<PhoneCapability> CREATOR = @@ -214,25 +213,24 @@ public final class PhoneCapability implements Parcelable { }; /** - * @return the maximum number of active packet-switched calls. For a dual - * sim dual standby (DSDS) modem it would be one, but for a dual sim dual active modem it + * @return the maximum subscriptions that can support simultaneous voice calls. For a dual + * sim dual standby (DSDS) device it would be one, but for a dual sim dual active device it * would be 2. * @hide */ @SystemApi - public @IntRange(from = 1) int getMaxActivePacketSwitchedVoiceCalls() { - return mMaxActivePsVoice; + public @IntRange(from = 1) int getMaxActiveVoiceSubscriptions() { + return mMaxActiveVoiceSubscriptions; } /** - * @return MaxActiveInternetData defines how many logical modems can have PS attached - * simultaneously. - * For example, for L+L modem it should be 2. + * @return the maximum subscriptions that can support simultaneous data connections. + * For example, for L+L device it should be 2. * @hide */ @SystemApi - public @IntRange(from = 1) int getMaxActiveInternetData() { - return mMaxActiveInternetData; + public @IntRange(from = 1) int getMaxActiveDataSubscriptions() { + return mMaxActiveDataSubscriptions; } /** @@ -254,13 +252,16 @@ public final class PhoneCapability implements Parcelable { } /** - * Return the device's NR capability. + * Return List of the device's NR capability. If the device doesn't support NR capability, + * then this api return empty array. + * @see DEVICE_NR_CAPABILITY_NSA + * @see DEVICE_NR_CAPABILITY_SA * - * @return {@link DeviceNrCapability} the device's NR capability. + * @return List of the device's NR capability. * @hide */ @SystemApi - public @DeviceNrCapability int getDeviceNrCapabilityBitmask() { - return mDeviceNrCapability; + public @NonNull @DeviceNrCapability int[] getDeviceNrCapabilities() { + return mDeviceNrCapabilities == null ? (new int[0]) : mDeviceNrCapabilities; } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index f7580d77186d..a46621a83c1e 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -630,7 +630,7 @@ public class SubscriptionManager { 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 @@ public class SubscriptionManager { * 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 d3246ca8ba6c..ebd07f73753b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15077,7 +15077,12 @@ public class TelephonyManager { * DataThrottlingRequest#DATA_THROTTLING_ACTION_NO_DATA_THROTTLING} can still be requested in * order to undo the mitigations above it (i.e {@link * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_VOICE_ONLY} and/or {@link - * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}). + * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}). </p> + * + * <p> In addition to the {@link Manifest.permission#MODIFY_PHONE_STATE} permission, callers of + * this API must also be listed in the device configuration as an authorized app in + * {@code packages/services/Telephony/res/values/config.xml} under the + * {@code thermal_mitigation_allowlisted_packages} key. </p> * * @param thermalMitigationRequest Thermal mitigation request. See {@link * ThermalMitigationRequest} for details. @@ -15096,7 +15101,8 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest); + return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest, + getOpPackageName()); } throw new IllegalStateException("telephony service is null."); } catch (RemoteException ex) { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 8ed9cffbe147..6184ffe02083 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2250,10 +2250,12 @@ interface ITelephony { * * @param subId the id of the subscription * @param thermalMitigationRequest holds the parameters necessary for the request. + * @param callingPackage the package name of the calling package. * @throws InvalidThermalMitigationRequestException if the parametes are invalid. */ int sendThermalMitigationRequest(int subId, - in ThermalMitigationRequest thermalMitigationRequest); + in ThermalMitigationRequest thermalMitigationRequest, + String callingPackage); /** * get the Generic Bootstrapping Architecture authentication keys diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt index 4f95ce585de2..b134fe737d05 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.InputEventReceiver 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 @@ private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) { assertEquals(expected.displayId, received.displayId) } -class TestInputEventReceiver(channel: InputChannel, looper: Looper) : - InputEventReceiver(channel, looper) { - companion object { - const val TAG = "TestInputEventReceiver" +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") } +} - var lastEvent: InputEvent? = null +class TestInputEventReceiver(channel: InputChannel, looper: Looper) : + InputEventReceiver(channel, looper) { + 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 val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>() - private var mFinishedSignal = CountDownLatch(1) 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 @@ class InputEventSenderAndReceiverTest { KeyEvent.KEYCODE_A, 0 /*repeat*/) val seq = 10 mSender.sendInputEvent(seq, key) - mSender.waitForFinish() + val receivedKey = mReceiver.getInputEvent() as KeyEvent + val finishedSignal = mSender.getFinishedSignal() // Check receiver - assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent) + assertKeyEvent(key, receivedKey) // Check sender - assertEquals(seq, mSender.finishedResult.seq) - assertEquals(true, mSender.finishedResult.handled) + assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) } } diff --git a/tests/UsesFeature2Test/AndroidManifest.xml b/tests/UsesFeature2Test/AndroidManifest.xml index 8caf4a158867..1f1a90958298 100644 --- a/tests/UsesFeature2Test/AndroidManifest.xml +++ b/tests/UsesFeature2Test/AndroidManifest.xml @@ -22,6 +22,9 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-feature android:name="android.hardware.sensor.accelerometer" /> <feature-group android:label="@string/minimal"> diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt index a4d8353d1253..fd126ad8c0b3 100644 --- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -44,7 +44,7 @@ class NetworkAgentConfigTest { 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 d40b88ca599f..e7718b546c1e 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.NET_CAPABILITY_WIFI_P2P; 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 @@ public class NetworkCapabilitiesTest { @Test public void testMaybeMarkCapabilitiesRestricted() { - // verify EIMS is restricted - assertEquals((1 << NET_CAPABILITY_EIMS) & RESTRICTED_CAPABILITIES, - (1 << NET_CAPABILITY_EIMS)); - - // verify CBS is also restricted - assertEquals((1 << NET_CAPABILITY_CBS) & RESTRICTED_CAPABILITIES, - (1 << NET_CAPABILITY_CBS)); - - // verify default is not restricted - assertEquals((1 << NET_CAPABILITY_INTERNET) & RESTRICTED_CAPABILITIES, 0); - - // just to see - assertEquals(RESTRICTED_CAPABILITIES & UNRESTRICTED_CAPABILITIES, 0); - // check that internet does not get restricted NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); @@ -329,7 +313,8 @@ public class NetworkCapabilitiesTest { if (isAtLeastS()) { netCap.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)); netCap.setUids(uids); - } else if (isAtLeastR()) { + } + if (isAtLeastR()) { netCap.setOwnerUid(123); netCap.setAdministratorUids(new int[] {5, 11}); } @@ -531,11 +516,22 @@ public class NetworkCapabilitiesTest { assertFalse(nc1.equalsNetCapabilities(nc2)); nc2.addUnwantedCapability(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)); + 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.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 +592,21 @@ public class NetworkCapabilitiesTest { // 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 +969,6 @@ public class NetworkCapabilitiesTest { 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 01eb514a1c81..61ef5bdca487 100644 --- a/tests/net/integration/src/android/net/TestNetworkStackClient.kt +++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt @@ -19,6 +19,7 @@ package android.net 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 @@ import kotlin.test.fail 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 db49e0b0047e..14dddcbd6ce7 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 @@ class ConnectivityServiceIntegrationTest { doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString()) networkStackClient = TestNetworkStackClient(realContext) - networkStackClient.init() networkStackClient.start() service = TestConnectivityService(makeDependencies()) diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 6fc605e269fe..36f205b72a62 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -64,6 +64,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.os.Process; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -219,8 +220,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - anyInt(), any(), nullable(String.class))).thenReturn(request); + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request); manager.requestNetwork(request, callback, handler); // callback triggers @@ -247,8 +248,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - anyInt(), any(), nullable(String.class))).thenReturn(req1); + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1); manager.requestNetwork(req1, callback, handler); // callback triggers @@ -265,8 +266,8 @@ public class ConnectivityManagerTest { verify(callback, timeout(100).times(0)).onLosing(any(), anyInt()); // callback can be registered again - when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - anyInt(), any(), nullable(String.class))).thenReturn(req2); + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2); manager.requestNetwork(req2, callback, handler); // callback triggers @@ -289,8 +290,8 @@ public class ConnectivityManagerTest { info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; when(mCtx.getApplicationInfo()).thenReturn(info); - when(mService.requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), - any(), nullable(String.class))).thenReturn(request); + when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), + anyInt(), any(), nullable(String.class))).thenReturn(request); Handler handler = new Handler(Looper.getMainLooper()); manager.requestNetwork(request, callback, handler); @@ -357,34 +358,40 @@ public class ConnectivityManagerTest { final NetworkCallback callback = new ConnectivityManager.NetworkCallback(); manager.requestNetwork(request, callback); - verify(mService).requestNetwork(eq(request.networkCapabilities), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); // Verify that register network callback does not calls requestNetwork at all. manager.registerNetworkCallback(request, callback); - verify(mService, never()).requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), - anyInt(), any(), any()); + verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), + anyInt(), anyInt(), any(), any()); verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); + Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + manager.registerDefaultNetworkCallback(callback); - verify(mService).requestNetwork(eq(null), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); - Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + manager.registerDefaultNetworkCallbackAsUid(42, callback, handler); + verify(mService).requestNetwork(eq(42), eq(null), + eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + manager.requestBackgroundNetwork(request, handler, callback); - verify(mService).requestNetwork(eq(request.networkCapabilities), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); manager.registerSystemDefaultNetworkCallback(callback, handler); - verify(mService).requestNetwork(eq(null), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 0c2fb4ec6f27..2af4117e6d89 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.NetworkRequest; 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.Uri; 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 @@ public class ConnectivityServiceTest { @Mock NetworkStatsManager mStatsManager; @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; - @Mock NetworkStackClient mNetworkStack; + @Mock NetworkStackClientBase mNetworkStack; @Mock PackageManager mPackageManager; @Mock UserManager mUserManager; @Mock NotificationManager mNotificationManager; @@ -1448,6 +1448,23 @@ public class ConnectivityServiceTest { }); } + private interface ExceptionalRunnable { + void run() throws Exception; + } + + private void withPermission(String permission, ExceptionalRunnable r) throws Exception { + if (mServiceContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + r.run(); + return; + } + try { + mServiceContext.setPermission(permission, PERMISSION_GRANTED); + r.run(); + } finally { + mServiceContext.setPermission(permission, PERMISSION_DENIED); + } + } + private static final int PRIMARY_USER = 0; private static final UidRange PRIMARY_UIDRANGE = UidRange.createForUser(UserHandle.of(PRIMARY_USER)); @@ -3811,8 +3828,9 @@ public class ConnectivityServiceTest { NetworkCapabilities networkCapabilities = new NetworkCapabilities(); networkCapabilities.addTransportType(TRANSPORT_WIFI) .setNetworkSpecifier(new MatchAllNetworkSpecifier()); - mService.requestNetwork(networkCapabilities, NetworkRequest.Type.REQUEST.ordinal(), - null, 0, null, ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE, + mService.requestNetwork(Process.INVALID_UID, networkCapabilities, + NetworkRequest.Type.REQUEST.ordinal(), null, 0, null, + ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE, mContext.getPackageName(), getAttributionTag()); }); @@ -4041,7 +4059,7 @@ public class ConnectivityServiceTest { } @Test - public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception { + public void testRegisterPrivilegedDefaultCallbacksRequireNetworkSettings() throws Exception { mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false /* validated */); @@ -4050,12 +4068,19 @@ public class ConnectivityServiceTest { assertThrows(SecurityException.class, () -> mCm.registerSystemDefaultNetworkCallback(callback, handler)); callback.assertNoCallback(); + assertThrows(SecurityException.class, + () -> mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler)); + callback.assertNoCallback(); mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); mCm.registerSystemDefaultNetworkCallback(callback, handler); callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); mCm.unregisterNetworkCallback(callback); + + mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); } private void setCaptivePortalMode(int mode) { @@ -7488,6 +7513,10 @@ public class ConnectivityServiceTest { final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback(); registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID); + final TestNetworkCallback vpnDefaultCallbackAsUid = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallbackAsUid(VPN_UID, vpnDefaultCallbackAsUid, + new Handler(ConnectivityThread.getInstanceLooper())); + final int uid = Process.myUid(); final int userId = UserHandle.getUserId(uid); final ArrayList<String> allowList = new ArrayList<>(); @@ -7507,6 +7536,7 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7520,6 +7550,7 @@ public class ConnectivityServiceTest { defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -7535,6 +7566,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); // The following requires that the UID of this test package is greater than VPN_UID. This // is always true in practice because a plain AOSP build with no apps installed has almost @@ -7556,6 +7588,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7577,6 +7610,7 @@ public class ConnectivityServiceTest { assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7589,6 +7623,7 @@ public class ConnectivityServiceTest { assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7604,6 +7639,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7616,6 +7652,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7629,6 +7666,7 @@ public class ConnectivityServiceTest { assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7640,6 +7678,7 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability. vpnUidDefaultCallback.assertNoCallback(); // VPN does not apply to VPN_UID + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7652,12 +7691,14 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertNull(mCm.getActiveNetwork()); mCm.unregisterNetworkCallback(callback); mCm.unregisterNetworkCallback(defaultCallback); mCm.unregisterNetworkCallback(vpnUidCallback); mCm.unregisterNetworkCallback(vpnUidDefaultCallback); + mCm.unregisterNetworkCallback(vpnDefaultCallbackAsUid); } private void setupLegacyLockdownVpn() { @@ -9806,8 +9847,8 @@ public class ConnectivityServiceTest { for (int reqTypeInt : invalidReqTypeInts) { assertThrows("Expect throws for invalid request type " + reqTypeInt, IllegalArgumentException.class, - () -> mService.requestNetwork(nc, reqTypeInt, null, 0, null, - ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE, + () -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0, + null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE, mContext.getPackageName(), getAttributionTag()) ); } @@ -10378,6 +10419,7 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback, TEST_WORK_PROFILE_APP_UID); + // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well. mServiceContext.setPermission( Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); } @@ -10397,7 +10439,7 @@ public class ConnectivityServiceTest { private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup) throws Exception { - final int testPackageNameUid = 123; + final int testPackageNameUid = TEST_PACKAGE_UID; final String testPackageName = "per.app.defaults.package"; setupMultipleDefaultNetworksForOemNetworkPreferenceTest( networkPrefToSetup, testPackageNameUid, testPackageName); @@ -10533,6 +10575,11 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(defaultNetworkCallback); defaultNetworkCallback.assertNoCallback(); + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(Manifest.permission.NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -10543,19 +10590,22 @@ public class ConnectivityServiceTest { null, mEthernetNetworkAgent.getNetwork()); - // At this point with a restricted network used, the available callback should trigger + // At this point with a restricted network used, the available callback should trigger. defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mEthernetNetworkAgent.getNetwork()); + otherUidDefaultCallback.assertNoCallback(); // Now bring down the default network which should trigger a LOST callback. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); // At this point, with no network is available, the lost callback should trigger defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Confirm we can unregister without issues. mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); } @Test @@ -10573,6 +10623,11 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(defaultNetworkCallback); defaultNetworkCallback.assertNoCallback(); + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(Manifest.permission.NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. // The active nai for the default is null at this point as this is a restricted network. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); @@ -10584,15 +10639,19 @@ public class ConnectivityServiceTest { defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mEthernetNetworkAgent.getNetwork()); + otherUidDefaultCallback.assertNoCallback(); // Now bring down the default network which should trigger a LOST callback. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + otherUidDefaultCallback.assertNoCallback(); // At this point, with no network is available, the lost callback should trigger defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Confirm we can unregister without issues. mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); } @Test @@ -10606,6 +10665,11 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(defaultNetworkCallback); defaultNetworkCallback.assertNoCallback(); + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(Manifest.permission.NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + // Setup a process different than the test process to use the default network. This means // that the defaultNetworkCallback won't be tracked by the per-app policy. setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref); @@ -10621,6 +10685,9 @@ public class ConnectivityServiceTest { defaultNetworkCallback.assertNoCallback(); assertDefaultNetworkCapabilities(userId /* no networks */); + // The other UID does have access, and gets a callback. + otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + // Bring up unrestricted cellular. This should now satisfy the default network. setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, @@ -10628,25 +10695,31 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.getNetwork()); // At this point with an unrestricted network used, the available callback should trigger + // The other UID is unaffected and remains on the paid network. defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCellNetworkAgent.getNetwork()); assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Now bring down the per-app network. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); - // Since the callback didn't use the per-app network, no callback should fire. + // Since the callback didn't use the per-app network, only the other UID gets a callback. + // Because the preference specifies no fallback, it does not switch to cellular. defaultNetworkCallback.assertNoCallback(); + otherUidDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); // Now bring down the default network. setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); // As this callback was tracking the default, this should now trigger. defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Confirm we can unregister without issues. mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); } /** |