diff options
632 files changed, 19054 insertions, 6388 deletions
diff --git a/Android.bp b/Android.bp index 7e68986ebeec..240d803ef337 100644 --- a/Android.bp +++ b/Android.bp @@ -369,6 +369,7 @@ filegroup { ":framework_native_aidl", ":gatekeeper_aidl", ":gsiservice_aidl", + ":idmap2_aidl", ":idmap2_core_aidl", ":incidentcompanion_aidl", ":inputconstants_aidl", @@ -402,6 +403,7 @@ filegroup { ":framework-mediaprovider-sources", ":framework-permission-sources", ":framework-permission-s-sources", + ":framework-scheduling-sources", ":framework-sdkextensions-sources", ":framework-statsd-sources", ":framework-tethering-srcs", @@ -422,6 +424,7 @@ java_library { "framework-mediaprovider.stubs.module_lib", "framework-permission.stubs.module_lib", "framework-permission-s.stubs.module_lib", + "framework-scheduling.stubs.module_lib", "framework-sdkextensions.stubs.module_lib", "framework-statsd.stubs.module_lib", "framework-tethering.stubs.module_lib", @@ -442,6 +445,7 @@ java_library { "framework-mediaprovider.impl", "framework-permission.impl", "framework-permission-s.impl", + "framework-scheduling.impl", "framework-sdkextensions.impl", "framework-statsd.impl", "framework-tethering.impl", diff --git a/StubLibraries.bp b/StubLibraries.bp index 3f2e89889912..4bd524f229ca 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -315,6 +315,7 @@ java_library_static { "framework-mediaprovider.stubs", "framework-permission.stubs", "framework-permission-s.stubs", + "framework-scheduling.stubs", "framework-sdkextensions.stubs", "framework-statsd.stubs", "framework-tethering.stubs", @@ -338,6 +339,7 @@ java_library_static { "framework-mediaprovider.stubs.system", "framework-permission.stubs.system", "framework-permission-s.stubs.system", + "framework-scheduling.stubs.system", "framework-sdkextensions.stubs.system", "framework-statsd.stubs.system", "framework-tethering.stubs.system", @@ -377,6 +379,7 @@ java_library_static { "framework-mediaprovider.stubs.system", "framework-permission.stubs.system", "framework-permission-s.stubs.system", + "framework-scheduling.stubs.system", "framework-sdkextensions.stubs.system", "framework-statsd.stubs.system", "framework-tethering.stubs.system", diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 8723515e1fdb..8fcd2f9bffb2 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -146,19 +146,17 @@ public final class AppSearchSession implements Closeable { * <p>It is a no-op to set the same schema as has been previously set; this is handled * efficiently. * - * <p>By default, documents are visible on platform surfaces. To opt out, call {@code - * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any - * visibility settings apply only to the schemas that are included in the {@code request}. - * Visibility settings for a schema type do not apply or persist across - * {@link SetSchemaRequest}s. + * <p>By default, documents are visible on platform surfaces. To opt out, call + * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as + * false. Any visibility settings apply only to the schemas that are included in the + * {@code request}. Visibility settings for a schema type do not persist across + * {@link #setSchema} calls. * * @param request The schema update request. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive errors resulting from setting the schema. If the * operation succeeds, the callback will be invoked with {@code null}. */ - // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are - // exposed. public void setSchema( @NonNull SetSchemaRequest request, @NonNull @CallbackExecutor Executor executor, diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index c369801a091f..a45fa39bd58d 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -41,6 +41,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -58,8 +59,11 @@ public class AppSearchManagerService extends SystemService { private PackageManagerInternal mPackageManagerInternal; private ImplInstanceManager mImplInstanceManager; - // Cache of unlocked user ids so we don't have to query UserManager service each time. - private final Set<Integer> mUnlockedUserIds = new ArraySet<>(); + // Cache of unlocked user ids so we don't have to query UserManager service each time. The + // "locked" suffix refers to the fact that access to the field should be locked; unrelated to + // the unlocked status of user ids. + @GuardedBy("mUnlockedUserIdsLocked") + private final Set<Integer> mUnlockedUserIdsLocked = new ArraySet<>(); public AppSearchManagerService(Context context) { super(context); @@ -74,7 +78,9 @@ public class AppSearchManagerService extends SystemService { @Override public void onUserUnlocked(@NonNull TargetUser user) { - mUnlockedUserIds.add(user.getUserIdentifier()); + synchronized (mUnlockedUserIdsLocked) { + mUnlockedUserIdsLocked.add(user.getUserIdentifier()); + } } private class Stub extends IAppSearchManager.Stub { @@ -503,9 +509,11 @@ public class AppSearchManagerService extends SystemService { } private void verifyUserUnlocked(int callingUserId) { - if (!mUnlockedUserIds.contains(callingUserId)) { - throw new IllegalStateException( - "User " + callingUserId + " is locked or not running."); + synchronized (mUnlockedUserIdsLocked) { + if (!mUnlockedUserIdsLocked.contains(callingUserId)) { + throw new IllegalStateException( + "User " + callingUserId + " is locked or not running."); + } } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index 5ea2a02b5b40..82319d4f353b 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -29,6 +29,7 @@ import android.os.storage.StorageManager; import android.util.SparseArray; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import java.io.File; @@ -43,7 +44,9 @@ public final class ImplInstanceManager { private static ImplInstanceManager sImplInstanceManager; - private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>(); + @GuardedBy("mInstancesLocked") + private final SparseArray<AppSearchImpl> mInstancesLocked = new SparseArray<>(); + private final String mGlobalQuerierPackage; private ImplInstanceManager(@NonNull String globalQuerierPackage) { @@ -81,19 +84,16 @@ public final class ImplInstanceManager { * @return An initialized {@link AppSearchImpl} for this user */ @NonNull - public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId) - throws AppSearchException { - AppSearchImpl instance = mInstances.get(userId); - if (instance == null) { - synchronized (ImplInstanceManager.class) { - instance = mInstances.get(userId); - if (instance == null) { - instance = createImpl(context, userId); - mInstances.put(userId, instance); - } + public AppSearchImpl getAppSearchImpl( + @NonNull Context context, @UserIdInt int userId) throws AppSearchException { + synchronized (mInstancesLocked) { + AppSearchImpl instance = mInstancesLocked.get(userId); + if (instance == null) { + instance = createImpl(context, userId); + mInstancesLocked.put(userId, instance); } + return instance; } - return instance; } private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId) diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java index 64dc972d301c..babcd25e3e26 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java @@ -332,10 +332,8 @@ public class VisibilityStore { for (Map.Entry<String, List<PackageIdentifier>> entry : schemasPackageAccessible.entrySet()) { for (int i = 0; i < entry.getValue().size(); i++) { - // TODO(b/169883602): remove the "placeholder" uri once upstream changes to relax - // nested document uri rules gets synced down. GenericDocument packageAccessibleDocument = - new GenericDocument.Builder(/*uri=*/ "placeholder", PACKAGE_ACCESSIBLE_TYPE) + new GenericDocument.Builder(/*uri=*/"", PACKAGE_ACCESSIBLE_TYPE) .setNamespace(NAMESPACE) .setPropertyString( PACKAGE_NAME_PROPERTY, diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java index 8e62c0e57f7d..b2ffd5b4b60c 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java @@ -86,11 +86,11 @@ public interface AppSearchSessionShim extends Closeable { * <p>It is a no-op to set the same schema as has been previously set; this is handled * efficiently. * - * <p>By default, documents are visible on platform surfaces. To opt out, call {@code - * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any - * visibility settings apply only to the schemas that are included in the {@code request}. - * Visibility settings for a schema type do not apply or persist across {@link - * SetSchemaRequest}s. + * <p>By default, documents are visible on platform surfaces. To opt out, call + * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as + * false. Any visibility settings apply only to the schemas that are included in the + * {@code request}. Visibility settings for a schema type do not persist across + * {@link #setSchema} calls. * * <p>Migration: make non-backwards-compatible changes will delete all stored documents in old * schema. You can save your documents by setting {@link @@ -116,8 +116,6 @@ public interface AppSearchSessionShim extends Closeable { * @see android.app.appsearch.AppSearchSchema.Migrator * @see android.app.appsearch.AppSearchMigrationHelper.Transformer */ - // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are - // exposed. @NonNull ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request); 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 82e967ae1a0b..3cefe65e45f9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -64,7 +64,7 @@ import java.util.List; * and which {@link JobServiceContext} to run each job on. */ class JobConcurrencyManager { - private static final String TAG = JobSchedulerService.TAG; + private static final String TAG = JobSchedulerService.TAG + ".Concurrency"; private static final boolean DEBUG = JobSchedulerService.DEBUG; static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_"; @@ -321,13 +321,14 @@ class JobConcurrencyManager { } } + /** Return {@code true} if the state was updated. */ @GuardedBy("mLock") - private void refreshSystemStateLocked() { + private boolean refreshSystemStateLocked() { final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis(); // Only refresh the information every so often. if (nowUptime < mNextSystemStateRefreshTime) { - return; + return false; } final long start = mStatLogger.getTime(); @@ -340,11 +341,14 @@ class JobConcurrencyManager { } mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start); + return true; } @GuardedBy("mLock") private void updateCounterConfigLocked() { - refreshSystemStateLocked(); + if (!refreshSystemStateLocked()) { + return; + } final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF; @@ -437,9 +441,10 @@ class JobConcurrencyManager { // (sharing the same Uid as nextPending) int minPriorityForPreemption = Integer.MAX_VALUE; int selectedContextId = -1; - int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending)); + int allWorkTypes = getJobWorkTypes(nextPending); + int workType = mWorkCountTracker.canJobStart(allWorkTypes); boolean startingJob = false; - for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { + for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) { JobStatus job = contextIdToJobMap[j]; int preferredUid = preferredUidForContext[j]; if (job == null) { @@ -483,7 +488,7 @@ class JobConcurrencyManager { if (startingJob) { // Increase the counters when we're going to start a job. workTypeForContext[selectedContextId] = workType; - mWorkCountTracker.stageJob(workType); + mWorkCountTracker.stageJob(workType, allWorkTypes); } } if (DEBUG) { @@ -578,8 +583,10 @@ class JobConcurrencyManager { JobStatus highestPriorityJob = null; int highPriWorkType = workType; + int highPriAllWorkTypes = workType; JobStatus backupJob = null; int backupWorkType = WORK_TYPE_NONE; + int backupAllWorkTypes = WORK_TYPE_NONE; for (int i = 0; i < pendingJobs.size(); i++) { final JobStatus nextPending = pendingJobs.get(i); @@ -589,11 +596,12 @@ class JobConcurrencyManager { if (worker.getPreferredUid() != nextPending.getUid()) { if (backupJob == null) { - int workAsType = - mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending)); + int allWorkTypes = getJobWorkTypes(nextPending); + int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType != WORK_TYPE_NONE) { backupJob = nextPending; backupWorkType = workAsType; + backupAllWorkTypes = allWorkTypes; } } continue; @@ -611,7 +619,8 @@ class JobConcurrencyManager { // reserved slots. We should just run the highest priority job we can find, // though it would be ideal to use an available WorkType slot instead of // overloading slots. - final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending)); + highPriAllWorkTypes = getJobWorkTypes(nextPending); + final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes); if (workAsType == WORK_TYPE_NONE) { // Just use the preempted job's work type since this new one is technically // replacing it anyway. @@ -624,7 +633,7 @@ class JobConcurrencyManager { if (DEBUG) { Slog.d(TAG, "Running job " + jobStatus + " as preemption"); } - mWorkCountTracker.stageJob(highPriWorkType); + mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes); startJobLocked(worker, highestPriorityJob, highPriWorkType); } else { if (DEBUG) { @@ -635,7 +644,7 @@ class JobConcurrencyManager { if (DEBUG) { Slog.d(TAG, "Running job " + jobStatus + " instead"); } - mWorkCountTracker.stageJob(backupWorkType); + mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes); startJobLocked(worker, backupJob, backupWorkType); } } @@ -647,6 +656,7 @@ class JobConcurrencyManager { // find. JobStatus highestPriorityJob = null; int highPriWorkType = workType; + int highPriAllWorkTypes = workType; for (int i = 0; i < pendingJobs.size(); i++) { final JobStatus nextPending = pendingJobs.get(i); @@ -654,7 +664,8 @@ class JobConcurrencyManager { continue; } - final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending)); + final int allWorkTypes = getJobWorkTypes(nextPending); + final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType == WORK_TYPE_NONE) { continue; } @@ -663,6 +674,7 @@ class JobConcurrencyManager { < nextPending.lastEvaluatedPriority) { highestPriorityJob = nextPending; highPriWorkType = workAsType; + highPriAllWorkTypes = allWorkTypes; } } @@ -672,7 +684,7 @@ class JobConcurrencyManager { if (DEBUG) { Slog.d(TAG, "About to run job: " + jobStatus); } - mWorkCountTracker.stageJob(highPriWorkType); + mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes); startJobLocked(worker, highestPriorityJob, highPriWorkType); } } @@ -1102,26 +1114,58 @@ class JobConcurrencyManager { } void incrementPendingJobCount(int workTypes) { + adjustPendingJobCount(workTypes, true); + } + + void decrementPendingJobCount(int workTypes) { + if (adjustPendingJobCount(workTypes, false) > 1) { + // We don't need to adjust reservations if only one work type was modified + // because that work type is the one we're using. + + // 0 is WORK_TYPE_NONE. + int workType = 1; + int rem = workTypes; + while (rem > 0) { + if ((rem & 1) != 0) { + maybeAdjustReservations(workType); + } + rem = rem >>> 1; + workType = workType << 1; + } + } + } + + /** Returns the number of WorkTypes that were modified. */ + private int adjustPendingJobCount(int workTypes, boolean add) { + final int adj = add ? 1 : -1; + + int numAdj = 0; // We don't know which type we'll classify the job as when we run it yet, so make sure // we have space in all applicable slots. if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) { - mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + 1); + mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj); + numAdj++; } if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) { - mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + 1); + mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj); + numAdj++; } if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { - mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1); + mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj); + numAdj++; } if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) { - mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1); + mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj); + numAdj++; } + + return numAdj; } - void stageJob(@WorkType int workType) { + void stageJob(@WorkType int workType, int allWorkTypes) { final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1; mNumStartingJobs.put(workType, newNumStartingJobs); - mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1)); + decrementPendingJobCount(allWorkTypes); if (newNumStartingJobs + mNumRunningJobs.get(workType) > mNumActuallyReservedSlots.get(workType)) { mNumUnspecializedRemaining--; 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 71fe55fb0640..96f3bcc58e8b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -3183,7 +3183,7 @@ public class JobSchedulerService extends com.android.server.SystemService TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw); pw.println(); job.dump(pw, " ", false, nowElapsed); - int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked()); + int priority = evaluateJobPriorityLocked(job); pw.print(" Evaluated priority: "); pw.println(JobInfo.getPriorityString(priority)); @@ -3349,7 +3349,7 @@ public class JobSchedulerService extends com.android.server.SystemService job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed); proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY, - evaluateJobPriorityLocked(jsc.getRunningJobLocked())); + evaluateJobPriorityLocked(job)); proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS, nowUptime - job.madeActive); 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 d15bae0274ac..da6f9fe0ace7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -20,6 +20,7 @@ import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.annotation.Nullable; import android.app.job.IJobCallback; import android.app.job.IJobService; import android.app.job.JobInfo; @@ -326,6 +327,7 @@ public final class JobServiceContext implements ServiceConnection { /** * Used externally to query the running job. Will return null if there is no job running. */ + @Nullable JobStatus getRunningJobLocked() { return mRunningJob; } diff --git a/apex/media/Android.bp b/apex/media/Android.bp index 5f1bd374df00..d6666f521b17 100644 --- a/apex/media/Android.bp +++ b/apex/media/Android.bp @@ -18,3 +18,10 @@ package { "//frameworks/av/apex/testing", ], } + +sdk { + name: "media-module-sdk", + java_sdk_libs: [ + "framework-media", + ], +} diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt index 67fa9bb55202..a2366df0660a 100644 --- a/apex/media/framework/api/current.txt +++ b/apex/media/framework/api/current.txt @@ -8,9 +8,10 @@ package android.media { method @NonNull public java.util.List<java.lang.String> getSupportedVideoMimeTypes(); method @NonNull public java.util.List<java.lang.String> getUnsupportedHdrTypes(); method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes(); - method public boolean isHdrTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException; + method public boolean isFormatSpecified(@NonNull String); + method public boolean isHdrTypeSupported(@NonNull String); method public boolean isSlowMotionSupported(); - method public boolean isVideoMimeTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException; + method public boolean isVideoMimeTypeSupported(@NonNull String); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR; } @@ -24,10 +25,6 @@ package android.media { method @NonNull public android.media.ApplicationMediaCapabilities build(); } - public static class ApplicationMediaCapabilities.FormatNotFoundException extends android.util.AndroidException { - ctor public ApplicationMediaCapabilities.FormatNotFoundException(@NonNull String); - } - public class MediaCommunicationManager { method @IntRange(from=1) public int getVersion(); } diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java index aefeab621778..685cf0dc7f77 100644 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java @@ -22,7 +22,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.util.AndroidException; import android.util.Log; import org.xmlpull.v1.XmlPullParser; @@ -79,17 +78,7 @@ import java.util.Set; public final class ApplicationMediaCapabilities implements Parcelable { private static final String TAG = "ApplicationMediaCapabilities"; - /** - * This exception is thrown when a given format is not specified in the media capabilities. - */ - public static class FormatNotFoundException extends AndroidException { - public FormatNotFoundException(@NonNull String format) { - super(format); - } - } - /** List of supported video codec mime types. */ - // TODO: init it with avc and mpeg4 as application is assuming to support them. private Set<String> mSupportedVideoMimeTypes = new HashSet<>(); /** List of unsupported video codec mime types. */ @@ -113,39 +102,54 @@ public final class ApplicationMediaCapabilities implements Parcelable { /** * Query if a video codec format is supported by the application. + * <p> + * If the application has not specified supporting the format or not, this will return false. + * Use {@link #isFormatSpecified(String)} to query if a format is specified or not. + * * @param videoMime The mime type of the video codec format. Must be the one used in * {@link MediaFormat#KEY_MIME}. * @return true if application supports the video codec format, false otherwise. - * @throws FormatNotFoundException if the application did not specify the codec either in the - * supported or unsupported formats. */ public boolean isVideoMimeTypeSupported( - @NonNull String videoMime) throws FormatNotFoundException { - if (mUnsupportedVideoMimeTypes.contains(videoMime.toLowerCase())) { - return false; - } else if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) { + @NonNull String videoMime) { + if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) { return true; - } else { - throw new FormatNotFoundException(videoMime); } + return false; } /** * Query if a HDR type is supported by the application. + * <p> + * If the application has not specified supporting the format or not, this will return false. + * Use {@link #isFormatSpecified(String)} to query if a format is specified or not. + * * @param hdrType The type of the HDR format. * @return true if application supports the HDR format, false otherwise. - * @throws FormatNotFoundException if the application did not specify the format either in the - * supported or unsupported formats. */ public boolean isHdrTypeSupported( - @NonNull @MediaFeature.MediaHdrType String hdrType) throws FormatNotFoundException { - if (mUnsupportedHdrTypes.contains(hdrType)) { - return false; - } else if (mSupportedHdrTypes.contains(hdrType)) { + @NonNull @MediaFeature.MediaHdrType String hdrType) { + if (mSupportedHdrTypes.contains(hdrType)) { return true; - } else { - throw new FormatNotFoundException(hdrType); } + return false; + } + + /** + * Query if a format is specified by the application. + * <p> + * The format could be either the video format or the hdr format. + * + * @param format The name of the format. + * @return true if application specifies the format, false otherwise. + */ + public boolean isFormatSpecified(@NonNull String format) { + if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format) + || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) { + return true; + + } + return false; } @Override diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java index ce7726a32152..c924d9a309d2 100644 --- a/apex/media/framework/java/android/media/MediaTranscodeManager.java +++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java @@ -1062,14 +1062,8 @@ public final class MediaTranscodeManager { "Source video format hint must be set!"); } - boolean supportHevc = false; - try { - supportHevc = mClientCaps.isVideoMimeTypeSupported( - MediaFormat.MIMETYPE_VIDEO_HEVC); - } catch (ApplicationMediaCapabilities.FormatNotFoundException ex) { - // Set to false if application did not specify. - supportHevc = false; - } + boolean supportHevc = mClientCaps.isVideoMimeTypeSupported( + MediaFormat.MIMETYPE_VIDEO_HEVC); if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals( mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) { return true; diff --git a/api/Android.bp b/api/Android.bp index d5c6bf6d024e..ac2f0831353c 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -37,6 +37,7 @@ genrule { ":framework-mediaprovider{.public.api.txt}", ":framework-permission{.public.api.txt}", ":framework-permission-s{.public.api.txt}", + ":framework-scheduling{.public.api.txt}", ":framework-sdkextensions{.public.api.txt}", ":framework-statsd{.public.api.txt}", ":framework-tethering{.public.api.txt}", @@ -75,6 +76,7 @@ genrule { ":framework-mediaprovider{.public.stubs.source}", ":framework-permission{.public.stubs.source}", ":framework-permission-s{.public.stubs.source}", + ":framework-scheduling{.public.stubs.source}", ":framework-sdkextensions{.public.stubs.source}", ":framework-statsd{.public.stubs.source}", ":framework-tethering{.public.stubs.source}", @@ -99,6 +101,7 @@ genrule { ":framework-mediaprovider{.public.removed-api.txt}", ":framework-permission{.public.removed-api.txt}", ":framework-permission-s{.public.removed-api.txt}", + ":framework-scheduling{.public.removed-api.txt}", ":framework-sdkextensions{.public.removed-api.txt}", ":framework-statsd{.public.removed-api.txt}", ":framework-tethering{.public.removed-api.txt}", @@ -133,6 +136,7 @@ genrule { ":framework-mediaprovider{.system.api.txt}", ":framework-permission{.system.api.txt}", ":framework-permission-s{.system.api.txt}", + ":framework-scheduling{.system.api.txt}", ":framework-sdkextensions{.system.api.txt}", ":framework-statsd{.system.api.txt}", ":framework-tethering{.system.api.txt}", @@ -167,6 +171,7 @@ genrule { ":framework-mediaprovider{.system.removed-api.txt}", ":framework-permission{.system.removed-api.txt}", ":framework-permission-s{.system.removed-api.txt}", + ":framework-scheduling{.system.removed-api.txt}", ":framework-sdkextensions{.system.removed-api.txt}", ":framework-statsd{.system.removed-api.txt}", ":framework-tethering{.system.removed-api.txt}", @@ -201,6 +206,7 @@ genrule { ":framework-mediaprovider{.module-lib.api.txt}", ":framework-permission{.module-lib.api.txt}", ":framework-permission-s{.module-lib.api.txt}", + ":framework-scheduling{.module-lib.api.txt}", ":framework-sdkextensions{.module-lib.api.txt}", ":framework-statsd{.module-lib.api.txt}", ":framework-tethering{.module-lib.api.txt}", @@ -234,6 +240,7 @@ genrule { ":framework-mediaprovider{.module-lib.removed-api.txt}", ":framework-permission{.module-lib.removed-api.txt}", ":framework-permission-s{.module-lib.removed-api.txt}", + ":framework-scheduling{.module-lib.removed-api.txt}", ":framework-sdkextensions{.module-lib.removed-api.txt}", ":framework-statsd{.module-lib.removed-api.txt}", ":framework-tethering{.module-lib.removed-api.txt}", diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index ed717c491467..4b7eda096e54 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -19,6 +19,7 @@ package com.android.commands.bmgr; import android.annotation.IntDef; import android.annotation.UserIdInt; import android.app.backup.BackupManager; +import android.app.backup.BackupManager.OperationType; import android.app.backup.BackupManagerMonitor; import android.app.backup.BackupProgress; import android.app.backup.BackupTransport; @@ -666,7 +667,7 @@ public class Bmgr { // The rest of the 'list' options work with a restore session on the current transport try { - mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP); if (mRestore == null) { System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; @@ -821,7 +822,7 @@ public class Bmgr { try { boolean didRestore = false; - mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP); if (mRestore == null) { System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; diff --git a/core/api/current.txt b/core/api/current.txt index 17f254fb820f..d8003f37fa6c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1295,6 +1295,7 @@ package android { field public static final int spinnerMode = 16843505; // 0x10102f1 field public static final int spinnerStyle = 16842881; // 0x1010081 field public static final int spinnersShown = 16843595; // 0x101034b + field public static final int splashScreenTheme = 16844336; // 0x1010630 field public static final int splitMotionEvents = 16843503; // 0x10102ef field public static final int splitName = 16844105; // 0x1010549 field public static final int splitTrack = 16843852; // 0x101044c @@ -1614,6 +1615,7 @@ package android { field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b field public static final int windowAnimationStyle = 16842926; // 0x10100ae field public static final int windowBackground = 16842836; // 0x1010054 + field public static final int windowBackgroundBlurRadius = 16844331; // 0x101062b field public static final int windowBackgroundFallback = 16844035; // 0x1010503 field public static final int windowBlurBehindEnabled = 16844316; // 0x101061c field public static final int windowBlurBehindRadius = 16844315; // 0x101061b @@ -1654,6 +1656,10 @@ package android { field public static final int windowShowAnimation = 16842934; // 0x10100b6 field public static final int windowShowWallpaper = 16843410; // 0x1010292 field public static final int windowSoftInputMode = 16843307; // 0x101022b + field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d + field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e + field public static final int windowSplashScreenBackground = 16844332; // 0x101062c + field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f field public static final int windowSplashscreenContent = 16844132; // 0x1010564 field @Deprecated public static final int windowSwipeToDismiss = 16843763; // 0x10103f3 field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c @@ -1740,6 +1746,9 @@ package android { field public static final int dialog_min_width_minor = 17104900; // 0x1050004 field public static final int notification_large_icon_height = 17104902; // 0x1050006 field public static final int notification_large_icon_width = 17104901; // 0x1050005 + field public static final int system_app_widget_background_radius = 17104904; // 0x1050008 + field public static final int system_app_widget_inner_radius = 17104905; // 0x1050009 + field public static final int system_app_widget_internal_padding = 17104906; // 0x105000a field public static final int thumbnail_height = 17104897; // 0x1050001 field public static final int thumbnail_width = 17104898; // 0x1050002 } @@ -3864,6 +3873,7 @@ package android.app { method @Nullable public android.net.Uri getReferrer(); method public int getRequestedOrientation(); method public final android.view.SearchEvent getSearchEvent(); + method @NonNull public final android.window.SplashScreen getSplashScreen(); method public int getTaskId(); method public final CharSequence getTitle(); method public final int getTitleColor(); @@ -5587,10 +5597,10 @@ package android.app { field public static final String EXTRA_PROGRESS = "android.progress"; field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; field public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; - field public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture"; field public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; field @Deprecated public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; + field public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = "android.showBigPictureWhenCollapsed"; field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; field public static final String EXTRA_SHOW_WHEN = "android.showWhen"; field @Deprecated public static final String EXTRA_SMALL_ICON = "android.icon"; @@ -6652,6 +6662,7 @@ package android.app { method @NonNull public java.time.LocalTime getCustomNightModeEnd(); method @NonNull public java.time.LocalTime getCustomNightModeStart(); method public int getNightMode(); + method public void setApplicationNightMode(int); method public void setCustomNightModeEnd(@NonNull java.time.LocalTime); method public void setCustomNightModeStart(@NonNull java.time.LocalTime); method public void setNightMode(int); @@ -6896,6 +6907,7 @@ package android.app.admin { method public void onLockTaskModeEntering(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String); method public void onLockTaskModeExiting(@NonNull android.content.Context, @NonNull android.content.Intent); method public void onNetworkLogsAvailable(@NonNull android.content.Context, @NonNull android.content.Intent, long, @IntRange(from=1) int); + method public void onOperationSafetyStateChanged(@NonNull android.content.Context, int, boolean); method @Deprecated public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent); method public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull android.os.UserHandle); method @Deprecated public void onPasswordExpiring(@NonNull android.content.Context, @NonNull android.content.Intent); @@ -7072,6 +7084,7 @@ package android.app.admin { method public boolean isProfileOwnerApp(String); method public boolean isProvisioningAllowed(@NonNull String); method public boolean isResetPasswordTokenActive(android.content.ComponentName); + method public boolean isSafeOperation(int); method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName); method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String); method public boolean isUniqueDeviceAttestationSupported(); @@ -7244,7 +7257,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE"; field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME"; field public static final String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI"; - field public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR"; + field @Deprecated public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR"; field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE"; field public static final String EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT"; field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER"; @@ -7300,6 +7313,7 @@ package android.app.admin { field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1 field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2 field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning"; + field public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; // 0x1 field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000 field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000 field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000 @@ -7336,7 +7350,6 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 - field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1 field public static final int WIPE_EUICC = 4; // 0x4 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 @@ -7490,7 +7503,7 @@ package android.app.admin { public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable { method public int describeContents(); - method public int getReason(); + method @NonNull public java.util.List<java.lang.Integer> getReasons(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR; } @@ -9817,6 +9830,7 @@ package android.content { method public int getMimeTypeCount(); method public long getTimestamp(); method public boolean hasMimeType(String); + method public boolean isStyledText(); method public void setExtras(android.os.PersistableBundle); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR; @@ -12775,6 +12789,7 @@ package android.content.pm { method @NonNull public android.content.pm.ShortcutInfo.Builder setPersons(@NonNull android.app.Person[]); method @NonNull public android.content.pm.ShortcutInfo.Builder setRank(int); method @NonNull public android.content.pm.ShortcutInfo.Builder setShortLabel(@NonNull CharSequence); + method @NonNull public android.content.pm.ShortcutInfo.Builder setStartingTheme(int); } public class ShortcutManager { @@ -21894,6 +21909,7 @@ package android.media { field public static final String KEY_COLOR_RANGE = "color-range"; field public static final String KEY_COLOR_STANDARD = "color-standard"; field public static final String KEY_COLOR_TRANSFER = "color-transfer"; + field public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request"; field public static final String KEY_COMPLEXITY = "complexity"; field public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED = "create-input-buffers-suspended"; field public static final String KEY_DURATION = "durationUs"; @@ -38074,6 +38090,10 @@ package android.service.notification { method public final void setNotificationsShown(String[]); method public final void snoozeNotification(String, long); method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel); + field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2 + field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1 + field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8 + field public static final int FLAG_FILTER_TYPE_SILENT = 4; // 0x4 field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 @@ -38082,6 +38102,7 @@ package android.service.notification { field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3 field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2 field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0 + field public static final String META_DATA_DEFAULT_FILTER_TYPES = "android.service.notification.default_filter_types"; field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1 field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3 field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2 @@ -49357,6 +49378,7 @@ package android.view { method public void setAllowEnterTransitionOverlap(boolean); method public void setAllowReturnTransitionOverlap(boolean); method public void setAttributes(android.view.WindowManager.LayoutParams); + method public void setBackgroundBlurRadius(int); method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setBackgroundDrawableResource(@DrawableRes int); method public void setCallback(android.view.Window.Callback); @@ -53591,16 +53613,19 @@ package android.widget { public class EdgeEffect { ctor public EdgeEffect(android.content.Context); + ctor public EdgeEffect(@NonNull android.content.Context, @Nullable android.util.AttributeSet); method public boolean draw(android.graphics.Canvas); method public void finish(); method @Nullable public android.graphics.BlendMode getBlendMode(); method @ColorInt public int getColor(); + method public float getDistance(); method public int getMaxHeight(); method public int getType(); method public boolean isFinished(); method public void onAbsorb(int); method public void onPull(float); method public void onPull(float, float); + method public float onPullDistance(float, float); method public void onRelease(); method public void setBlendMode(@Nullable android.graphics.BlendMode); method public void setColor(@ColorInt int); @@ -55810,6 +55835,25 @@ package android.widget.inline { } +package android.window { + + public interface SplashScreen { + method public void setOnExitAnimationListener(@Nullable android.window.SplashScreen.OnExitAnimationListener); + } + + public static interface SplashScreen.OnExitAnimationListener { + method public void onSplashScreenExit(@NonNull android.window.SplashScreenView); + } + + public final class SplashScreenView extends android.widget.FrameLayout { + method public long getIconAnimationDurationMillis(); + method public long getIconAnimationStartMillis(); + method @Nullable public android.view.View getIconView(); + method public void remove(); + } + +} + package javax.microedition.khronos.egl { public interface EGL { diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 4e256254c04a..51d513966140 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -223,6 +223,14 @@ package android.net { field @NonNull public final java.util.List<java.lang.String> underlyingIfaces; } + public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { + ctor public VpnTransportInfo(int); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR; + field public final int type; + } + } package android.os { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fdd1e6660697..491679173677 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -82,6 +82,7 @@ package android { field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION"; field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"; + field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"; field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN"; field public static final String CREATE_USERS = "android.permission.CREATE_USERS"; field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; @@ -245,6 +246,7 @@ package android { field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT"; field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE"; field public static final String SHUTDOWN = "android.permission.SHUTDOWN"; + field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS"; field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"; field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND"; field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"; @@ -678,6 +680,7 @@ package android.app { } public static class Notification.Action implements android.os.Parcelable { + field public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; // 0xc field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb } @@ -870,7 +873,6 @@ package android.app.admin { } public class DevicePolicyManager { - method public boolean canAdminGrantSensorsPermissionsForUser(int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); @@ -905,8 +907,8 @@ package android.app.admin { field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME"; - field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI"; - field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL"; + field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI"; + field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL"; field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME"; field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE"; field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER"; @@ -2523,7 +2525,8 @@ package android.content.pm { field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur"; - field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; + field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; + field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg"; @@ -7084,6 +7087,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); + method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -7105,6 +7109,10 @@ package android.net { field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd } + public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { + method public void onComplete(); + } + @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); method @Deprecated public void onTetheringFailed(); @@ -7189,6 +7197,7 @@ package android.net { method public void close(); method @NonNull public String getInterfaceName(); method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException; } public static class IpSecTransform.Builder { @@ -7332,6 +7341,7 @@ package android.net { method @NonNull public int[] getAdministratorUids(); method @Nullable public String getSsid(); method @NonNull public int[] getTransportTypes(); + method public boolean isPrivateDnsBroken(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 @@ -7346,6 +7356,7 @@ package android.net { method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int); method @NonNull public android.net.NetworkCapabilities build(); + method @NonNull public android.net.NetworkCapabilities.Builder clearAll(); method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int); method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]); @@ -7461,6 +7472,26 @@ package android.net { ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); } + 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(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.OemNetworkPreferences> CREATOR; + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4 + field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0 + } + + public static final class OemNetworkPreferences.Builder { + ctor public OemNetworkPreferences.Builder(); + ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences); + method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int); + method @NonNull public android.net.OemNetworkPreferences build(); + method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String); + } + public abstract class QosCallback { ctor public QosCallback(); method public void onError(@NonNull android.net.QosCallbackException); @@ -8569,6 +8600,7 @@ package android.os { public class SystemConfigManager { method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + method @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public int[] getSystemPermissionUids(@NonNull String); } public class SystemProperties { @@ -9519,10 +9551,12 @@ package android.security.keystore { } public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec { + method @Nullable public int[] getAttestationIds(); method public int getNamespace(); } public static final class KeyGenParameterSpec.Builder { + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationIds(@NonNull int[]); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int); method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int); } @@ -10355,6 +10389,7 @@ package android.service.storage { method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onEndSession(@NonNull String) throws java.io.IOException; method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException; + method public long onGetAnrDelayMillis(@NonNull String, int); method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException; method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException; field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2 @@ -11046,6 +11081,8 @@ package android.telephony { } public static final class CarrierConfigManager.Wifi { + field public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL = "wifi.avoid_5ghz_softap_for_laa_bool"; + field public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL = "wifi.avoid_5ghz_wifi_direct_for_laa_bool"; field public static final String KEY_HOTSPOT_MAX_CLIENT_COUNT = "wifi.hotspot_maximum_client_count"; field public static final String KEY_PREFIX = "wifi."; field public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED = "wifi.suggestion_ssid_list_with_mac_randomization_disabled"; @@ -11947,6 +11984,7 @@ package android.telephony { field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1 field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4 field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3 + field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED"; field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE"; field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 632b10f3330e..29be5c6e4681 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -391,11 +391,11 @@ package android.app.admin { method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); + method @NonNull public static String operationSafetyReasonToString(int); method @NonNull public static String operationToString(int); method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int); method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int); - method @NonNull public static String unsafeOperationReasonToString(int); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb @@ -425,6 +425,7 @@ package android.app.admin { field public static final int OPERATION_REMOVE_KEY_PAIR = 28; // 0x1c field public static final int OPERATION_REMOVE_USER = 6; // 0x6 field public static final int OPERATION_REQUEST_BUGREPORT = 29; // 0x1d + field public static final int OPERATION_SAFETY_REASON_NONE = -1; // 0xffffffff field public static final int OPERATION_SET_ALWAYS_ON_VPN_PACKAGE = 30; // 0x1e field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10 @@ -460,7 +461,6 @@ package android.app.admin { field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4 field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7 field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5 - field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff } public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable { @@ -914,6 +914,8 @@ package android.hardware.devicestate { method @NonNull public int[] getSupportedStates(); method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener); method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); + field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff + field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0 } public static interface DeviceStateManager.DeviceStateListener { @@ -1346,6 +1348,12 @@ package android.net { field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } + public class NetworkPolicyManager { + method public boolean getRestrictBackground(); + method @NonNull public static String resolveNetworkId(@NonNull android.net.wifi.WifiConfiguration); + method public void setRestrictBackground(boolean); + } + public class NetworkStack { method public static void setServiceForTest(@Nullable android.os.IBinder); } @@ -2721,6 +2729,10 @@ package android.window { field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2 } + public final class SplashScreenView extends android.widget.FrameLayout { + method @Nullable public android.view.View getBrandingView(); + } + public final class StartingWindowInfo implements android.os.Parcelable { ctor public StartingWindowInfo(); method public int describeContents(); @@ -2740,6 +2752,7 @@ package android.window { public class TaskOrganizer extends android.window.WindowOrganizer { ctor public TaskOrganizer(); method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder); + method @BinderThread public void copySplashScreenView(int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]); diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 3c67e44ff41f..87fb5b1b3b2e 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -487,6 +487,8 @@ GetterSetterNames: android.location.LocationRequest#isLocationSettingsIgnored(): GetterSetterNames: android.location.LocationRequest#isLowPowerMode(): +GetterSetterNames: android.net.NetworkPolicyManager#getRestrictBackground(): + Symmetric method for `setRestrictBackground` must be named `isRestrictBackground`; was `getRestrictBackground` GetterSetterNames: android.os.IncidentReportArgs#isAll(): GetterSetterNames: android.service.notification.NotificationStats#setDirectReplied(): diff --git a/core/java/android/accounts/OWNERS b/core/java/android/accounts/OWNERS index ea5fd36702f9..8dcc04a27af6 100644 --- a/core/java/android/accounts/OWNERS +++ b/core/java/android/accounts/OWNERS @@ -3,7 +3,6 @@ dementyev@google.com sandrakwan@google.com hackbod@google.com svetoslavganov@google.com -moltmann@google.com fkupolov@google.com yamasani@google.com omakoto@google.com diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4728f11a402d..992d054737b9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -139,6 +139,8 @@ import android.view.translation.UiTranslationController; import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; +import android.window.SplashScreen; +import android.window.SplashScreenView; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -961,6 +963,10 @@ public class Activity extends ContextThemeWrapper private UiTranslationController mUiTranslationController; + private SplashScreen mSplashScreen; + /** @hide */ + SplashScreenView mSplashScreenView; + private final WindowControllerCallback mWindowControllerCallback = new WindowControllerCallback() { /** @@ -1603,6 +1609,23 @@ public class Activity extends ContextThemeWrapper } /** + * Get the interface that activity use to talk to the splash screen. + * @see SplashScreen + */ + public final @NonNull SplashScreen getSplashScreen() { + return getOrCreateSplashScreen(); + } + + private SplashScreen getOrCreateSplashScreen() { + synchronized (this) { + if (mSplashScreen == null) { + mSplashScreen = new SplashScreen.SplashScreenImpl(this); + } + return mSplashScreen; + } + } + + /** * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with * the attribute {@link android.R.attr#persistableMode} set to * <code>persistAcrossReboots</code>. diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index 401f8cc13bad..e3b5e9a32324 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -46,9 +46,9 @@ public class ActivityClient { } /** Reports {@link Activity#onResume()} is done. */ - public void activityResumed(IBinder token) { + public void activityResumed(IBinder token, boolean handleSplashScreenExit) { try { - getActivityClientController().activityResumed(token); + getActivityClientController().activityResumed(token, handleSplashScreenExit); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -488,6 +488,17 @@ public class ActivityClient { } } + /** + * Reports the splash screen view has attached to client. + */ + void reportSplashScreenAttached(IBinder token) { + try { + getActivityClientController().splashScreenAttached(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + public static ActivityClient getInstance() { return sInstance.get(); } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 28da1c3a3eb7..73cc13c82bcb 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -160,6 +160,12 @@ public class ActivityOptions { public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener"; /** + * Specific a theme for a splash screen window. + * @hide + */ + public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme"; + + /** * Callback for when the last frame of the animation is played. * @hide */ @@ -398,6 +404,7 @@ public class ActivityOptions { private IBinder mLaunchCookie; private IRemoteTransition mRemoteTransition; private boolean mOverrideTaskTransition; + private int mSplashScreenThemeResId; /** * Create an ActivityOptions specifying a custom animation to run when @@ -1147,6 +1154,7 @@ public class ActivityOptions { mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder( KEY_REMOTE_TRANSITION)); mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION); + mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME); } /** @@ -1333,6 +1341,14 @@ public class ActivityOptions { } /** + * Gets whether the activity want to be launched as other theme for the splash screen. + * @hide + */ + public int getSplashScreenThemeResId() { + return mSplashScreenThemeResId; + } + + /** * Sets whether the activity is to be launched into LockTask mode. * * Use this option to start an activity in LockTask mode. Note that only apps permitted by @@ -1838,6 +1854,9 @@ public class ActivityOptions { if (mOverrideTaskTransition) { b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition); } + if (mSplashScreenThemeResId != 0) { + b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId); + } return b; } diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 70fa4445479b..233f737b8e0f 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -19,6 +19,7 @@ package android.app; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; @@ -37,6 +38,7 @@ import android.os.ServiceManager; import android.util.DisplayMetrics; import android.util.Singleton; import android.view.RemoteAnimationDefinition; +import android.window.SplashScreenView.SplashScreenViewParcelable; import java.util.List; @@ -243,6 +245,22 @@ public class ActivityTaskManager { } /** + * Notify the server that splash screen of the given task has been copied" + * + * @param taskId Id of task to handle the material to reconstruct the splash screen view. + * @param parcelable Used to reconstruct the view, null means the surface is un-copyable. + * @hide + */ + public void onSplashScreenViewCopyFinished(int taskId, + @Nullable SplashScreenViewParcelable parcelable) { + try { + getService().onSplashScreenViewCopyFinished(taskId, parcelable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the default limit on the number of recents that an app can make. * @hide */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bb6a774cbee2..3d9f6123963f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -171,12 +171,15 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewManager; import android.view.ViewRootImpl; +import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.translation.TranslationSpec; import android.webkit.WebView; +import android.window.SplashScreen; +import android.window.SplashScreenView; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -185,6 +188,7 @@ import com.android.internal.content.ReferrerIntent; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.DecorView; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.Preconditions; @@ -227,6 +231,7 @@ import java.util.Map; import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -285,6 +290,7 @@ public final class ActivityThread extends ClientTransactionHandler { /** Use background GC policy and default JIT threshold. */ private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1; + private static final int REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT = 5000; /** * Denotes an invalid sequence number corresponding to a process state change. */ @@ -486,6 +492,8 @@ public final class ActivityThread extends ClientTransactionHandler { final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>(); + private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal; + final GcIdler mGcIdler = new GcIdler(); final PurgeIdler mPurgeIdler = new PurgeIdler(); @@ -1930,6 +1938,8 @@ public final class ActivityThread extends ClientTransactionHandler { public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; + public static final int REMOVE_SPLASH_SCREEN_VIEW = 172; + String codeToString(int code) { if (DEBUG_MESSAGES) { switch (code) { @@ -1976,6 +1986,8 @@ public final class ActivityThread extends ClientTransactionHandler { case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART"; case FINISH_INSTRUMENTATION_WITHOUT_RESTART: return "FINISH_INSTRUMENTATION_WITHOUT_RESTART"; + case REMOVE_SPLASH_SCREEN_VIEW: + return "REMOVE_SPLASH_SCREEN_VIEW"; } } return Integer.toString(code); @@ -2169,6 +2181,9 @@ public final class ActivityThread extends ClientTransactionHandler { case FINISH_INSTRUMENTATION_WITHOUT_RESTART: handleFinishInstrumentationWithoutRestart(); break; + case REMOVE_SPLASH_SCREEN_VIEW: + handleRemoveSplashScreenView((ActivityClientRecord) msg.obj); + break; } Object obj = msg.obj; if (obj instanceof SomeArgs) { @@ -3955,6 +3970,106 @@ public final class ActivityThread extends ClientTransactionHandler { } /** + * Register a splash screen manager to this process. + */ + public void registerSplashScreenManager( + @NonNull SplashScreen.SplashScreenManagerGlobal manager) { + synchronized (this) { + mSplashScreenGlobal = manager; + } + } + + @Override + public boolean isHandleSplashScreenExit(@NonNull IBinder token) { + synchronized (this) { + return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token); + } + } + + @Override + public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, + @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) { + final DecorView decorView = (DecorView) r.window.peekDecorView(); + if (parcelable != null && decorView != null) { + createSplashScreen(r, decorView, parcelable); + } else { + // shouldn't happen! + Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach"); + } + } + + private void createSplashScreen(ActivityClientRecord r, DecorView decorView, + SplashScreenView.SplashScreenViewParcelable parcelable) { + final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity); + final SplashScreenView view = builder.createFromParcel(parcelable).build(); + decorView.addView(view); + view.cacheRootWindow(r.window); + view.makeSystemUIColorsTransparent(); + r.activity.mSplashScreenView = view; + view.requestLayout(); + // Ensure splash screen view is shown before remove the splash screen window. + final ViewRootImpl impl = decorView.getViewRootImpl(); + final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled(); + final AtomicBoolean notified = new AtomicBoolean(); + if (hardwareEnabled) { + final Runnable frameCommit = new Runnable() { + @Override + public void run() { + view.post(() -> { + if (!notified.get()) { + view.getViewTreeObserver().unregisterFrameCommitCallback(this); + ActivityClient.getInstance().reportSplashScreenAttached( + r.token); + notified.set(true); + } + }); + } + }; + view.getViewTreeObserver().registerFrameCommitCallback(frameCommit); + } else { + final ViewTreeObserver.OnDrawListener onDrawListener = + new ViewTreeObserver.OnDrawListener() { + @Override + public void onDraw() { + view.post(() -> { + if (!notified.get()) { + view.getViewTreeObserver().removeOnDrawListener(this); + ActivityClient.getInstance().reportSplashScreenAttached( + r.token); + notified.set(true); + } + }); + } + }; + view.getViewTreeObserver().addOnDrawListener(onDrawListener); + } + } + + @Override + public void handOverSplashScreenView(@NonNull ActivityClientRecord r) { + if (r.activity.mSplashScreenView != null) { + Message msg = mH.obtainMessage(H.REMOVE_SPLASH_SCREEN_VIEW, r); + mH.sendMessageDelayed(msg, REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT); + synchronized (this) { + if (mSplashScreenGlobal != null) { + mSplashScreenGlobal.dispatchOnExitAnimation(r.token, + r.activity.mSplashScreenView); + } + } + } + } + + /** + * Force remove splash screen view. + */ + private void handleRemoveSplashScreenView(@NonNull ActivityClientRecord r) { + if (r.activity.mSplashScreenView != null) { + r.activity.mSplashScreenView.remove(); + r.activity.mSplashScreenView = null; + } + } + + /** * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then * return to its previous state. This allows activities that rely on onUserLeaveHint instead of * onPictureInPictureRequested to enter picture-in-picture. @@ -5174,6 +5289,11 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_DESTROY); mLastReportedWindowingMode.remove(r.activity.getActivityToken()); schedulePurgeIdler(); + synchronized (this) { + if (mSplashScreenGlobal != null) { + mSplashScreenGlobal.tokenDestroyed(r.token); + } + } // updatePendingActivityConfiguration() reads from mActivities to update // ActivityClientRecord which runs in a different thread. Protect modifications to // mActivities to avoid race. diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 0e1c827145d2..cf5fd148c030 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -28,6 +28,7 @@ import android.content.res.Configuration; import android.os.IBinder; import android.util.MergedConfiguration; import android.view.DisplayAdjustments.FixedRotationAdjustments; +import android.window.SplashScreenView.SplashScreenViewParcelable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; @@ -158,6 +159,16 @@ public abstract class ClientTransactionHandler { /** Request that an activity enter picture-in-picture. */ public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r); + /** Whether the activity want to handle splash screen exit animation */ + public abstract boolean isHandleSplashScreenExit(@NonNull IBinder token); + + /** Attach a splash screen window view to the top of the activity */ + public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, + @NonNull SplashScreenViewParcelable parcelable); + + /** Hand over the splash screen window view to the activity */ + public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r); + /** Perform activity launch. */ public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent); diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 9d3286fa271c..bb743b89e00f 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -34,7 +34,7 @@ import com.android.internal.policy.IKeyguardDismissCallback; */ interface IActivityClientController { oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling); - oneway void activityResumed(in IBinder token); + oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit); oneway void activityTopResumedStateLost(); /** * Notifies that the activity has completed paused. This call is not one-way because it can make @@ -142,4 +142,7 @@ interface IActivityClientController { * on the back stack. */ oneway void onBackPressedOnTaskRoot(in IBinder token); + + /** Reports that the splash screen view has attached to activity. */ + oneway void splashScreenAttached(in IBinder token); } diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 38a3e70b3742..542f754ce364 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -70,6 +70,7 @@ import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.window.IWindowOrganizerController; +import android.window.SplashScreenView; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; @@ -348,4 +349,10 @@ interface IActivityTaskManager { * Clears launch params for given packages. */ void clearLaunchParamsForPackages(in List<String> packageNames); + + /** + * A splash screen view has copied. + */ + void onSplashScreenViewCopyFinished(int taskId, + in SplashScreenView.SplashScreenViewParcelable material); } diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index 0ba5beccbf32..f71eebdc66ec 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -62,6 +62,16 @@ interface IUiModeManager { int getNightMode(); /** + * Sets the dark mode for the given application. This setting is persisted and will override the + * system configuration for this application. + * 1 - notnight mode + * 2 - night mode + * 3 - automatic mode switching + * @throws RemoteException + */ + void setApplicationNightMode(in int mode); + + /** * Tells if UI mode is locked or not. */ boolean isUiModeLocked(); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 050f34a21477..77daf8ddf08f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1208,7 +1208,8 @@ public class Notification implements Parcelable * of a {@link BigPictureStyle} notification. This will replace a * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided. */ - public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture"; + public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = + "android.showBigPictureWhenCollapsed"; /** * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded @@ -1603,6 +1604,14 @@ public class Notification implements Parcelable @SystemApi public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; + /** + * {@code SemanticAction}: Mark content as a potential phishing attempt. + * Note that this is only for use by the notification assistant services. + * @hide + */ + @SystemApi + public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; + private final Bundle mExtras; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Icon mIcon; @@ -2314,7 +2323,8 @@ public class Notification implements Parcelable SEMANTIC_ACTION_THUMBS_UP, SEMANTIC_ACTION_THUMBS_DOWN, SEMANTIC_ACTION_CALL, - SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY + SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY, + SEMANTIC_ACTION_CONVERSATION_IS_PHISHING }) @Retention(RetentionPolicy.SOURCE) public @interface SemanticAction {} @@ -7059,7 +7069,7 @@ public class Notification implements Parcelable private Icon mBigLargeIcon; private boolean mBigLargeIconSet = false; private CharSequence mPictureContentDescription; - private boolean mPromotePicture; + private boolean mShowBigPictureWhenCollapsed; public BigPictureStyle() { } @@ -7124,7 +7134,7 @@ public class Notification implements Parcelable */ @NonNull public BigPictureStyle showBigPictureWhenCollapsed(boolean show) { - mPromotePicture = show; + mShowBigPictureWhenCollapsed = show; return this; } @@ -7195,7 +7205,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeContentView(boolean increasedHeight) { - if (mPicture == null || !mPromotePicture) { + if (mPicture == null || !mShowBigPictureWhenCollapsed) { return super.makeContentView(increasedHeight); } @@ -7225,7 +7235,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - if (mPicture == null || !mPromotePicture) { + if (mPicture == null || !mShowBigPictureWhenCollapsed) { return super.makeHeadsUpContentView(increasedHeight); } @@ -7309,7 +7319,7 @@ public class Notification implements Parcelable extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION, mPictureContentDescription); } - extras.putBoolean(EXTRA_PROMOTE_PICTURE, mPromotePicture); + extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); extras.putParcelable(EXTRA_PICTURE, mPicture); } @@ -7330,7 +7340,7 @@ public class Notification implements Parcelable extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION); } - mPromotePicture = extras.getBoolean(EXTRA_PROMOTE_PICTURE); + mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED); mPicture = extras.getParcelable(EXTRA_PICTURE); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d5e95708a805..c047fc21413d 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -128,11 +128,13 @@ import android.net.EthernetManager; import android.net.IEthernetManager; import android.net.IIpSecService; import android.net.INetworkPolicyManager; +import android.net.IVpnManager; import android.net.IpSecManager; import android.net.NetworkPolicyManager; import android.net.NetworkScoreManager; import android.net.NetworkWatchlistManager; import android.net.TetheringManager; +import android.net.VpnManager; import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; import android.net.nsd.INsdManager; @@ -384,6 +386,15 @@ public final class SystemServiceRegistry { ctx, () -> ServiceManager.getService(Context.TETHERING_SERVICE)); }}); + registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class, + new CachedServiceFetcher<VpnManager>() { + @Override + public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE); + IVpnManager service = IVpnManager.Stub.asInterface(b); + return new VpnManager(ctx, service); + }}); + registerService(Context.VCN_MANAGEMENT_SERVICE, VcnManager.class, new CachedServiceFetcher<VcnManager>() { @Override diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 938ce0d56933..9019ddf941d9 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -343,7 +343,9 @@ public class TaskInfo { // TopActivityToken and bounds are important if top activity is in size compat && (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken)) && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds() - .equals(that.configuration.windowConfiguration.getBounds())); + .equals(that.configuration.windowConfiguration.getBounds())) + && (!topActivityInSizeCompat || configuration.getLayoutDirection() + == that.configuration.getLayoutDirection()); } /** diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index e1c262caf44f..9b99ab8e31cb 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -477,11 +477,13 @@ public class UiModeManager { * Changes to night mode take effect globally and will result in a configuration change * (and potentially an Activity lifecycle event) being applied to all running apps. * Developers interested in an app-local implementation of night mode should consider using - * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} to manage the - * -night qualifier locally. + * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or + * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the + * backward compatible implementation. * * @param mode the night mode to set * @see #getNightMode() + * @see #setApplicationNightMode(int) */ public void setNightMode(@NightMode int mode) { if (mService != null) { @@ -494,6 +496,44 @@ public class UiModeManager { } /** + * Sets and persist the night mode for this application. + * <p> + * The mode can be one of: + * <ul> + * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into + * {@code notnight} mode</li> + * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into + * {@code night} mode</li> + * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between + * {@code night} and {@code notnight} based on the custom time set (or default)</li> + * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between + * {@code night} and {@code notnight} based on the device's current + * location and certain other sensors</li> + * </ul> + * <p> + * Changes to night mode take effect locally and will result in a configuration change + * (and potentially an Activity lifecycle event) being applied to this application. The mode + * is persisted for this application until it is either modified by the application, the + * user clears the data for the application, or this application is uninstalled. + * <p> + * Developers interested in a non-persistent app-local implementation of night mode should + * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} + * to manage the -night qualifier locally. + * + * @param mode the night mode to set + * @see #setNightMode(int) + */ + public void setApplicationNightMode(@NightMode int mode) { + if (mService != null) { + try { + mService.setApplicationNightMode(mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** * Returns the currently configured night mode. * <p> * May be one of: diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index d175a66e90ea..4dbff0c06423 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -16,6 +16,8 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.OperationSafetyReason; + import android.accounts.AccountManager; import android.annotation.BroadcastBehavior; import android.annotation.IntDef; @@ -35,6 +37,7 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.security.KeyChain; +import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -72,8 +75,8 @@ import java.lang.annotation.RetentionPolicy; * </div> */ public class DeviceAdminReceiver extends BroadcastReceiver { - private static String TAG = "DevicePolicy"; - private static boolean localLOGV = false; + private static final String TAG = "DevicePolicy"; + private static final boolean LOCAL_LOGV = false; /** * This is the primary action that a device administrator must implement to be @@ -509,6 +512,36 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE"; + /** + * Broadcast action: notify the admin that the state of operations that can be unsafe because + * of a given reason (specified by the {@link #EXTRA_OPERATION_SAFETY_REASON} {@code int} extra) + * has changed (the new value is specified by the {@link #EXTRA_OPERATION_SAFETY_STATE} + * {@code boolean} extra). + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_OPERATION_SAFETY_STATE_CHANGED = + "android.app.action.OPERATION_SAFETY_STATE_CHANGED"; + + /** + * An {@code int} extra specifying an {@link OperationSafetyReason}. + * + * @hide + */ + public static final String EXTRA_OPERATION_SAFETY_REASON = + "android.app.extra.OPERATION_SAFETY_REASON"; + + /** + * An {@code boolean} extra specifying whether an operation will fail due to a + * {@link OperationSafetyReason}. {@code true} means operations that rely on that reason are + * safe, while {@code false} means they're unsafe. + * + * @hide + */ + public static final String EXTRA_OPERATION_SAFETY_STATE = + "android.app.extra.OPERATION_SAFETY_STATE"; + private DevicePolicyManager mManager; private ComponentName mWho; @@ -1018,6 +1051,51 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Called to notify the state of operations that can be unsafe to execute has changed. + * + * <p><b>Note:/b> notice that the operation safety state might change between the time this + * callback is received and the operation's method on {@link DevicePolicyManager} is called, so + * calls to the latter could still throw a {@link UnsafeStateException} even when this method + * is called with {@code isSafe} as {@code true} + * + * @param context the running context as per {@link #onReceive} + * @param reason the reason an operation could be unsafe. + * @param isSafe whether the operation is safe to be executed. + */ + public void onOperationSafetyStateChanged(@NonNull Context context, + @OperationSafetyReason int reason, boolean isSafe) { + if (LOCAL_LOGV) { + Log.v(TAG, String.format("onOperationSafetyStateChanged(): %s=%b", + DevicePolicyManager.operationSafetyReasonToString(reason), isSafe)); + } + } + + private void onOperationSafetyStateChanged(Context context, Intent intent) { + if (!hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_REASON) + || !hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_STATE)) { + return; + } + + int reason = intent.getIntExtra(EXTRA_OPERATION_SAFETY_REASON, + DevicePolicyManager.OPERATION_SAFETY_REASON_NONE); + if (!DevicePolicyManager.isValidOperationSafetyReason(reason)) { + Log.wtf(TAG, "Received invalid reason on " + intent.getAction() + ": " + reason); + return; + } + boolean isSafe = intent.getBooleanExtra(EXTRA_OPERATION_SAFETY_STATE, + /* defaultValue=*/ false); + + onOperationSafetyStateChanged(context, reason, isSafe); + } + + private boolean hasRequiredExtra(Intent intent, String extra) { + if (intent.hasExtra(extra)) return true; + + Log.wtf(TAG, "Missing '" + extra + "' on intent " + intent); + return false; + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -1025,6 +1103,9 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { String action = intent.getAction(); + if (LOCAL_LOGV) { + Log.v(TAG, "onReceive(): received " + action + " on user " + context.getUserId()); + } if (ACTION_PASSWORD_CHANGED.equals(action)) { onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); @@ -1092,6 +1173,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { onTransferAffiliatedProfileOwnershipComplete(context, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) { + onOperationSafetyStateChanged(context, intent); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ff41d1c84e91..3b80f83aab0d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -206,7 +206,6 @@ public class DevicePolicyManager { * {@link android.os.Build.VERSION_CODES#N}</li> * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> * <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li> * <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li> @@ -250,7 +249,6 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li> * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> * </ul> * * <p>If provisioning fails, the device returns to its previous state. @@ -289,7 +287,6 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li> * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li> * <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li> * </ul> @@ -388,8 +385,6 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li> * <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li> @@ -438,8 +433,6 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li> * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li> * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li> * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> @@ -654,7 +647,10 @@ public class DevicePolicyManager { * * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} or * {@link #ACTION_PROVISION_MANAGED_DEVICE}. + * + * @deprecated Color customization is no longer supported in the provisioning flow. */ + @Deprecated public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR"; @@ -905,8 +901,10 @@ public class DevicePolicyManager { * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * + * @deprecated This extra is no longer respected in the provisioning flow. * @hide */ + @Deprecated @SystemApi public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL"; @@ -930,9 +928,11 @@ public class DevicePolicyManager { * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} * or {@link #ACTION_PROVISION_FINANCED_DEVICE} * + * @deprecated This extra is no longer respected in the provisioning flow. * @hide */ @SystemApi + @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI"; @@ -1300,10 +1300,11 @@ public class DevicePolicyManager { * A value for {@link #EXTRA_PROVISIONING_SUPPORTED_MODES} indicating that provisioning is * organization-owned. * - * <p>Using this value will cause the admin app's {@link #ACTION_GET_PROVISIONING_MODE} - * activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra - * contain {@link #PROVISIONING_MODE_MANAGED_PROFILE} and {@link - * #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}. + * <p>Using this value indicates the admin app can only be provisioned in either a + * fully-managed device or a corporate-owned work profile. This will cause the admin app's + * {@link #ACTION_GET_PROVISIONING_MODE} activity to have the {@link + * #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link + * #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}. * * <p>Also, if this value is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link @@ -2922,33 +2923,61 @@ public class DevicePolicyManager { return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation); } - private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_"; + private static final String PREFIX_OPERATION_SAFETY_REASON = "OPERATION_SAFETY_REASON_"; /** @hide */ - @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = { - UNSAFE_OPERATION_REASON_NONE, - UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION + @IntDef(prefix = PREFIX_OPERATION_SAFETY_REASON, value = { + OPERATION_SAFETY_REASON_NONE, + OPERATION_SAFETY_REASON_DRIVING_DISTRACTION }) @Retention(RetentionPolicy.SOURCE) - public static @interface UnsafeOperationReason { + public static @interface OperationSafetyReason { } /** @hide */ @TestApi - public static final int UNSAFE_OPERATION_REASON_NONE = -1; + public static final int OPERATION_SAFETY_REASON_NONE = -1; /** * Indicates that a {@link UnsafeStateException} was thrown because the operation would distract * the driver of the vehicle. */ - public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; + public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; /** @hide */ @NonNull @TestApi - public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) { + public static String operationSafetyReasonToString(@OperationSafetyReason int reason) { return DebugUtils.constantToString(DevicePolicyManager.class, - PREFIX_UNSAFE_OPERATION_REASON, reason); + PREFIX_OPERATION_SAFETY_REASON, reason); + } + + /** @hide */ + public static boolean isValidOperationSafetyReason(@OperationSafetyReason int reason) { + return reason == OPERATION_SAFETY_REASON_DRIVING_DISTRACTION; + } + + /** + * Checks if it's safe to run operations that can be affected by the given {@code reason}. + * + * <p><b>Note:/b> notice that the operation safety state might change between the time this + * method returns and the operation's method is called, so calls to the latter could still throw + * a {@link UnsafeStateException} even when this method returns {@code true}. + * + * @param reason currently, only supported reason is + * {@link #OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}. + * + * @return whether it's safe to run operations that can be affected by the given {@code reason}. + */ + // TODO(b/173541467): should it throw SecurityException if caller is not admin? + public boolean isSafeOperation(@OperationSafetyReason int reason) { + if (mService == null) return false; + + try { + return mService.isSafeOperation(reason); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @hide */ @@ -11689,8 +11718,11 @@ public class DevicePolicyManager { } /** - * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to - * control the network logging feature. + * Called by a device owner, profile owner of a managed profile or delegated app with + * {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature. + * + * <p> When network logging is enabled by a profile owner, the network logs will only include + * work profile network activity, not activity on the personal profile. * * <p> Network logs contain DNS lookup and connect() library call events. The following library * functions are recorded while network logging is active: @@ -11730,7 +11762,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or * {@code null} if called by a delegated app. * @param enabled whether network logging should be enabled or not. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner or profile owner. * @see #setAffiliationIds * @see #retrieveNetworkLogs */ @@ -11744,14 +11776,16 @@ public class DevicePolicyManager { } /** - * Return whether network logging is enabled by a device owner. + * Return whether network logging is enabled by a device owner or profile owner of + * a managed profile. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING} * or has MANAGE_USERS permission. - * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise. - * @throws SecurityException if {@code admin} is not a device owner and caller has - * no MANAGE_USERS permission + * @return {@code true} if network logging is enabled by device owner or profile owner, + * {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner or profile owner and + * caller has no MANAGE_USERS permission */ public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) { throwIfParentInstance("isNetworkLoggingEnabled"); @@ -11763,9 +11797,14 @@ public class DevicePolicyManager { } /** - * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve - * the most recent batch of network logging events. - * A device owner has to provide a batchToken provided as part of + * Called by device owner, profile owner of a managed profile or delegated app with + * {@link #DELEGATION_NETWORK_LOGGING} to retrieve the most recent batch of + * network logging events. + * + * <p> When network logging is enabled by a profile owner, the network logs will only include + * work profile network activity, not activity on the personal profile. + * + * A device owner or profile owner has to provide a batchToken provided as part of * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the * token of the most recent available batch of logs, {@code null} will be returned. * @@ -11777,11 +11816,11 @@ public class DevicePolicyManager { * after the device device owner has been notified via * {@link DeviceAdminReceiver#onNetworkLogsAvailable}. * - * <p>If a secondary user or profile is created, calling this method will throw a - * {@link SecurityException} until all users become affiliated again. It will also no longer be - * possible to retrieve the network logs batch with the most recent batchToken provided - * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See - * {@link DevicePolicyManager#setAffiliationIds}. + * <p>If the caller is not a profile owner and a secondary user or profile is created, calling + * this method will throw a {@link SecurityException} until all users become affiliated again. + * It will also no longer be possible to retrieve the network logs batch with the most recent + * batchToken provided by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. + * See {@link DevicePolicyManager#setAffiliationIds}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or * {@code null} if called by a delegated app. @@ -11789,8 +11828,9 @@ public class DevicePolicyManager { * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns * {@code null} if the batch represented by batchToken is no longer available or if * logging is disabled. - * @throws SecurityException if {@code admin} is not a device owner, or there is at least one - * profile or secondary user that is not affiliated with the device. + * @throws SecurityException if {@code admin} is not a device owner, profile owner or if the + * {@code admin} is not a profile owner and there is at least one profile or secondary user + * that is not affiliated with the device. * @see #setAffiliationIds * @see DeviceAdminReceiver#onNetworkLogsAvailable */ @@ -11909,11 +11949,12 @@ public class DevicePolicyManager { } /** - * Called by the system to get the time at which the device owner last retrieved network logging - * events. + * Called by the system to get the time at which the device owner or profile owner of a + * managed profile last retrieved network logging events. * - * @return the time at which the device owner most recently retrieved network logging events, in - * milliseconds since epoch; -1 if network logging events were never retrieved. + * @return the time at which the device owner or profile owner most recently retrieved network + * logging events, in milliseconds since epoch; -1 if network logging events were + * never retrieved. * @throws SecurityException if the caller is not the device owner, does not hold the * MANAGE_USERS permission and is not the system. * @@ -13157,7 +13198,7 @@ public class DevicePolicyManager { @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { if (mService != null) { try { mService.setNextOperationSafety(operation, reason); @@ -13329,24 +13370,11 @@ public class DevicePolicyManager { */ public boolean canAdminGrantSensorsPermissions() { throwIfParentInstance("canAdminGrantSensorsPermissions"); - return canAdminGrantSensorsPermissionsForUser(myUserId()); - } - - /** - * Returns true if the admin can control grants of sensors-related permissions, for - * a given user. - * - * @hide - * @param userId The ID of the user to check. - * @return if the admin may grant these permissions, false otherwise. - */ - @SystemApi - public boolean canAdminGrantSensorsPermissionsForUser(int userId) { if (mService == null) { return false; } try { - return mService.canAdminGrantSensorsPermissionsForUser(userId); + return mService.canAdminGrantSensorsPermissionsForUser(myUserId()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index a0d2977cf09a..67f5c366bc14 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.content.ComponentName; import android.content.Intent; import android.os.UserHandle; @@ -255,4 +256,14 @@ public abstract class DevicePolicyManagerInternal { * {@link #supportsResetOp(int)} is true. */ public abstract void resetOp(int op, String packageName, @UserIdInt int userId); + + /** + * Notifies the system that an unsafe operation reason has changed. + * + * @throws IllegalArgumentException if {@code checker} is not the same as set on + * {@code DevicePolicyManagerService}. + */ + public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, + @OperationSafetyReason int reason, boolean isSafe); + } diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java index 6c6f2aa15ab7..17b74b1ab400 100644 --- a/core/java/android/app/admin/DevicePolicySafetyChecker.java +++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java @@ -17,7 +17,7 @@ package android.app.admin; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import com.android.internal.os.IResultReceiver; @@ -31,15 +31,20 @@ public interface DevicePolicySafetyChecker { /** * Returns whether the given {@code operation} can be safely executed at the moment. */ - @UnsafeOperationReason + @OperationSafetyReason int getUnsafeOperationReason(@DevicePolicyOperation int operation); /** + * Return whether it's safe to run operations that can be affected by the given {@code reason}. + */ + boolean isSafeOperation(@OperationSafetyReason int reason); + + /** * Returns a new exception for when the given {@code operation} cannot be safely executed. */ @NonNull default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { return new UnsafeStateException(operation, reason); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 89f30cc821ab..032cf2483cd3 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -492,6 +492,7 @@ interface IDevicePolicyManager { boolean canProfileOwnerResetPasswordWhenLocked(int userId); void setNextOperationSafety(int operation, int reason); + boolean isSafeOperation(int reason); String getEnrollmentSpecificId(String callerPackage); void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId); diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java index 56eeb06e8cc0..f1f652601c66 100644 --- a/core/java/android/app/admin/UnsafeStateException.java +++ b/core/java/android/app/admin/UnsafeStateException.java @@ -15,18 +15,20 @@ */ package android.app.admin; -import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION; -import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString; +import static android.app.admin.DevicePolicyManager.isValidOperationSafetyReason; import android.annotation.NonNull; import android.annotation.TestApi; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.Preconditions; +import java.util.Arrays; +import java.util.List; + /** * Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it * was not safe to be executed at that moment. @@ -39,17 +41,15 @@ import com.android.internal.util.Preconditions; public final class UnsafeStateException extends IllegalStateException implements Parcelable { private final @DevicePolicyOperation int mOperation; - private final @UnsafeOperationReason int mReason; + private final @OperationSafetyReason int mReason; /** @hide */ @TestApi public UnsafeStateException(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { super(); - Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION, - "invalid reason %d, must be %d (%s)", reason, - UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION, - unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION)); + Preconditions.checkArgument(isValidOperationSafetyReason(reason), "invalid reason %d", + reason); mOperation = operation; mReason = reason; } @@ -61,19 +61,20 @@ public final class UnsafeStateException extends IllegalStateException implements } /** - * Gets the reason the operation is unsafe. + * Gets the reasons the operation is unsafe. * * @return currently, only valid reason is - * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}. + * {@link android.app.admin.DevicePolicyManager#OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}. */ - public @UnsafeOperationReason int getReason() { - return mReason; + @NonNull + public List<Integer> getReasons() { + return Arrays.asList(mReason); } /** @hide */ @Override public String getMessage() { - return DevicePolicyManager.unsafeOperationReasonToString(mReason); + return DevicePolicyManager.operationSafetyReasonToString(mReason); } @Override diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 7e232acbdcdd..85cfe835c28d 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -401,7 +401,8 @@ public abstract class BackupAgent extends ContextWrapper { * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) */ public void onFullBackup(FullBackupDataOutput data) throws IOException { - FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this); + FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this, + mOperationType); if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) { return; } @@ -624,7 +625,8 @@ public abstract class BackupAgent extends ContextWrapper { if (includeMap == null || includeMap.size() == 0) { // Do entire sub-tree for the provided token. fullBackupFileTree(packageName, domainToken, - FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken), + FullBackup.getBackupScheme(this, mOperationType) + .tokenToDirectoryPath(domainToken), filterSet, traversalExcludeSet, data); } else if (includeMap.get(domainToken) != null) { // This will be null if the xml parsing didn't yield any rules for @@ -795,7 +797,8 @@ public abstract class BackupAgent extends ContextWrapper { ArraySet<String> systemExcludes, FullBackupDataOutput output) { // Pull out the domain and set it aside to use when making the tarball. - String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); + String domainPath = FullBackup.getBackupScheme(this, mOperationType) + .tokenToDirectoryPath(domain); if (domainPath == null) { // Should never happen. return; @@ -911,7 +914,7 @@ public abstract class BackupAgent extends ContextWrapper { return true; } - FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this); + FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType); if (!bs.isFullBackupContentEnabled()) { if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { Log.v(FullBackup.TAG_XML_PARSER, @@ -985,7 +988,8 @@ public abstract class BackupAgent extends ContextWrapper { + " domain=" + domain + " relpath=" + path + " mode=" + mode + " mtime=" + mtime); - basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); + basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath( + domain); if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { mode = -1; // < 0 is a token to skip attempting a chmod() } diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index dae565e12fd7..673de8fa7c8c 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -361,7 +361,36 @@ public class BackupManager { try { // All packages, current transport IRestoreSession binder = - sService.beginRestoreSessionForUser(mContext.getUserId(), null, null); + sService.beginRestoreSessionForUser(mContext.getUserId(), null, null, + OperationType.BACKUP); + if (binder != null) { + session = new RestoreSession(mContext, binder); + } + } catch (RemoteException e) { + Log.e(TAG, "beginRestoreSession() couldn't connect"); + } + } + return session; + } + + /** + * Begin the process of restoring data from backup. See the + * {@link android.app.backup.RestoreSession} class for documentation on that process. + * + * @param operationType Type of the operation, see {@link OperationType} + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BACKUP) + public RestoreSession beginRestoreSession(@OperationType int operationType) { + RestoreSession session = null; + checkServiceBinder(); + if (sService != null) { + try { + // All packages, current transport + IRestoreSession binder = + sService.beginRestoreSessionForUser(mContext.getUserId(), null, null, + operationType); if (binder != null) { session = new RestoreSession(mContext, binder); } @@ -772,7 +801,7 @@ public class BackupManager { @SystemApi @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[] packages, BackupObserver observer) { - return requestBackup(packages, observer, null, 0); + return requestBackup(packages, observer, null, 0, OperationType.BACKUP); } /** @@ -797,6 +826,31 @@ public class BackupManager { @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[] packages, BackupObserver observer, BackupManagerMonitor monitor, int flags) { + return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP); + } + + /** + * Request an immediate backup, providing an observer to which results of the backup operation + * will be published. The Android backup system will decide for each package whether it will + * be full app data backup or key/value-pair-based backup. + * + * <p>If this method returns {@link BackupManager#SUCCESS}, the OS will attempt to backup all + * provided packages using the remote transport. + * + * @param packages List of package names to backup. + * @param observer The {@link BackupObserver} to receive callbacks during the backup + * operation. Could be {@code null}. + * @param monitor The {@link BackupManagerMonitorWrapper} to receive callbacks of important + * events during the backup operation. Could be {@code null}. + * @param flags {@link #FLAG_NON_INCREMENTAL_BACKUP}. + * @param operationType {@link OperationType} + * @return {@link BackupManager#SUCCESS} on success; nonzero on error. + * @throws IllegalArgumentException on null or empty {@code packages} param. + * @hide + */ + @RequiresPermission(android.Manifest.permission.BACKUP) + public int requestBackup(String[] packages, BackupObserver observer, + BackupManagerMonitor monitor, int flags, @OperationType int operationType) { checkServiceBinder(); if (sService != null) { try { @@ -806,7 +860,8 @@ public class BackupManager { BackupManagerMonitorWrapper monitorWrapper = monitor == null ? null : new BackupManagerMonitorWrapper(monitor); - return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags); + return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags, + operationType); } catch (RemoteException e) { Log.e(TAG, "requestBackup() couldn't connect"); } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 742d05c1ffa4..f7ed6f1f2feb 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -16,6 +16,9 @@ package android.app.backup; +import static android.app.backup.BackupManager.OperationType; + +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -41,6 +44,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -90,27 +94,61 @@ public class FullBackup { "fakeClientSideEncryption"; /** + * Identify {@link BackupScheme} object by package and operation type + * (see {@link OperationType}) it corresponds to. + */ + private static class BackupSchemeId { + final String mPackageName; + @OperationType final int mOperationType; + + BackupSchemeId(String packageName, @OperationType int operationType) { + mPackageName = packageName; + mOperationType = operationType; + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mOperationType); + } + + @Override + public boolean equals(@Nullable Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + BackupSchemeId that = (BackupSchemeId) object; + return Objects.equals(mPackageName, that.mPackageName) && + Objects.equals(mOperationType, that.mOperationType); + } + } + + /** * @hide */ @UnsupportedAppUsage static public native int backupToTar(String packageName, String domain, String linkdomain, String rootpath, String path, FullBackupDataOutput output); - private static final Map<String, BackupScheme> kPackageBackupSchemeMap = - new ArrayMap<String, BackupScheme>(); + private static final Map<BackupSchemeId, BackupScheme> kPackageBackupSchemeMap = + new ArrayMap<>(); - static synchronized BackupScheme getBackupScheme(Context context) { + static synchronized BackupScheme getBackupScheme(Context context, + @OperationType int operationType) { + BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType); BackupScheme backupSchemeForPackage = - kPackageBackupSchemeMap.get(context.getPackageName()); + kPackageBackupSchemeMap.get(backupSchemeId); if (backupSchemeForPackage == null) { - backupSchemeForPackage = new BackupScheme(context); - kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage); + backupSchemeForPackage = new BackupScheme(context, operationType); + kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage); } return backupSchemeForPackage; } public static BackupScheme getBackupSchemeForTest(Context context) { - BackupScheme testing = new BackupScheme(context); + BackupScheme testing = new BackupScheme(context, OperationType.BACKUP); testing.mExcludes = new ArraySet(); testing.mIncludes = new ArrayMap(); return testing; @@ -236,6 +274,7 @@ public class FullBackup { private final static String TAG_EXCLUDE = "exclude"; final int mFullBackupContent; + @OperationType final int mOperationType; final PackageManager mPackageManager; final StorageManager mStorageManager; final String mPackageName; @@ -354,8 +393,9 @@ public class FullBackup { */ ArraySet<PathWithRequiredFlags> mExcludes; - BackupScheme(Context context) { + BackupScheme(Context context, @OperationType int operationType) { mFullBackupContent = context.getApplicationInfo().fullBackupContent; + mOperationType = operationType; mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); mPackageManager = context.getPackageManager(); mPackageName = context.getPackageName(); diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index bf5be95c4ab0..e1bbc08e72f3 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -547,9 +547,11 @@ interface IBackupManager { * set can be restored. * @param transportID The name of the transport to use for the restore operation. * May be null, in which case the current active transport is used. + * @param operationType Type of the operation, see {@link BackupManager#OperationType} * @return An interface to the restore session, or null on error. */ - IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID); + IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID, + int operationType); /** * Notify the backup manager that a BackupAgent has completed the operation @@ -678,7 +680,7 @@ interface IBackupManager { * {@link android.app.backup.IBackupManager.requestBackupForUser} for the calling user id. */ int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, - int flags); + int flags, int operationType); /** * Cancel all running backups. After this call returns, no currently running backups will diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index 28b73406b877..ab38832458d6 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -20,8 +20,16 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.compat.Compatibility; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; +import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.IPlatformCompat; + +import java.util.Map; + /** * CompatChanges APIs - to be used by platform code only (including mainline * modules). @@ -89,4 +97,25 @@ public final class CompatChanges { return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid)); } + /** + * Set an app compat override for a given package. This will check whether the caller is allowed + * to perform this operation on the given apk and build. Only the installer package is allowed + * to set overrides on a non-debuggable final build and a non-test apk. + * + * @param packageName The package name of the app in question. + * @param overrides A map from changeId to the override applied for this change id. + * @hide + */ + @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG) + public static void setPackageOverride(String packageName, + Map<Long, PackageOverride> overrides) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides); + try { + platformCompat.setOverridesFromInstaller(config, packageName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java new file mode 100644 index 000000000000..9f97cd41128a --- /dev/null +++ b/core/java/android/app/compat/PackageOverride.java @@ -0,0 +1,211 @@ +/* + * 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.app.compat; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An app compat override applied to a given package and change id pairing. + * + * A package override contains a list of version ranges with the desired boolean value of + * the override for the app in this version range. Ranges can be open ended in either direction. + * An instance of PackageOverride gets created via {@link Builder} and is immutable once created. + * + * @hide + */ +public class PackageOverride implements Parcelable { + + @IntDef({ + VALUE_UNDEFINED, + VALUE_ENABLED, + VALUE_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + /** @hide */ + public @interface EvaluatedOverride { + } + + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * this PackageOverride does not define the value of the override for the given version. + * @hide + */ + public static final int VALUE_UNDEFINED = 0; + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * the override evaluates to {@code true} for the given version. + * @hide + */ + public static final int VALUE_ENABLED = 1; + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * the override evaluates to {@code fakse} for the given version. + * @hide + */ + public static final int VALUE_DISABLED = 2; + + private final long mMinVersionCode; + private final long mMaxVersionCode; + private final boolean mEnabled; + + private PackageOverride(long minVersionCode, + long maxVersionCode, + boolean enabled) { + this.mMinVersionCode = minVersionCode; + this.mMaxVersionCode = maxVersionCode; + this.mEnabled = enabled; + } + + private PackageOverride(Parcel in) { + this(in.readLong(), in.readLong(), in.readBoolean()); + } + + /** + * Evaluate the override for the given {@code versionCode}. If no override is defined for + * the specified version code, {@link #VALUE_UNDEFINED} is returned. + * @hide + */ + public @EvaluatedOverride int evaluate(long versionCode) { + if (versionCode >= mMinVersionCode && versionCode <= mMaxVersionCode) { + return mEnabled ? VALUE_ENABLED : VALUE_DISABLED; + } + return VALUE_UNDEFINED; + } + + /** + * Evaluate the override independent of version code, i.e. only return an evaluated value if + * this range covers all versions, otherwise {@link #VALUE_UNDEFINED} is returned. + * @hide + */ + public int evaluateForAllVersions() { + if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) { + return mEnabled ? VALUE_ENABLED : VALUE_DISABLED; + } + return VALUE_UNDEFINED; + } + + /** Returns the minimum version code the override applies to. */ + public long getMinVersionCode() { + return mMinVersionCode; + } + + /** Returns the minimum version code the override applies from. */ + public long getMaxVersionCode() { + return mMaxVersionCode; + } + + /** Returns the enabled value for the override. */ + public boolean getEnabled() { + return mEnabled; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mMinVersionCode); + dest.writeLong(mMaxVersionCode); + dest.writeBoolean(mEnabled); + } + + /** @hide */ + @Override + public String toString() { + if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) { + return Boolean.toString(mEnabled); + } + return String.format("[%d,%d,%b]", mMinVersionCode, mMaxVersionCode, mEnabled); + } + + /** @hide */ + public static final Creator<PackageOverride> CREATOR = + new Creator<PackageOverride>() { + + @Override + public PackageOverride createFromParcel(Parcel in) { + return new PackageOverride(in); + } + + @Override + public PackageOverride[] newArray(int size) { + return new PackageOverride[size]; + } + }; + + /** + * Builder to construct a PackageOverride. + */ + public static class Builder { + private long mMinVersionCode = Long.MIN_VALUE; + private long mMaxVersionCode = Long.MAX_VALUE; + private boolean mEnabled; + + /** + * Sets the minimum version code the override should apply from. + * + * default value: {@code Long.MIN_VALUE}. + */ + public Builder setMinVersionCode(long minVersionCode) { + mMinVersionCode = minVersionCode; + return this; + } + + /** + * Sets the maximum version code the override should apply to. + * + * default value: {@code Long.MAX_VALUE}. + */ + public Builder setMaxVersionCode(long maxVersionCode) { + mMaxVersionCode = maxVersionCode; + return this; + } + + /** + * Sets whether the override should be enabled for the given version range. + * + * default value: {@code false}. + */ + public Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + + /** + * Build the {@link PackageOverride}. + * + * @throws IllegalArgumentException if {@code minVersionCode} is larger than + * {@code maxVersionCode}. + */ + public PackageOverride build() { + if (mMinVersionCode > mMaxVersionCode) { + throw new IllegalArgumentException("minVersionCode must not be larger than " + + "maxVersionCode"); + } + return new PackageOverride(mMinVersionCode, mMaxVersionCode, mEnabled); + } + }; +} diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index d451599cc7b0..e6fdc006615a 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -60,7 +60,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { public void postExecute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { // TODO(lifecycler): Use interface callback instead of actual implementation. - ActivityClient.getInstance().activityResumed(token); + ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token)); } @Override diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java new file mode 100644 index 000000000000..5374984d31d0 --- /dev/null +++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java @@ -0,0 +1,105 @@ +/* + * 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 android.app.servertransaction; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; +import android.app.ClientTransactionHandler; +import android.os.Parcel; +import android.window.SplashScreenView.SplashScreenViewParcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Transfer a splash screen view to an Activity. + * @hide + */ +public class TransferSplashScreenViewStateItem extends ActivityTransactionItem { + + private SplashScreenViewParcelable mSplashScreenViewParcelable; + private @TransferRequest int mRequest; + + @IntDef(value = { + ATTACH_TO, + HANDOVER_TO + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TransferRequest {} + // request client to attach the view on it. + public static final int ATTACH_TO = 0; + // tell client that you can handle the splash screen view. + public static final int HANDOVER_TO = 1; + + @Override + public void execute(@NonNull ClientTransactionHandler client, + @NonNull ActivityThread.ActivityClientRecord r, + PendingTransactionActions pendingActions) { + switch (mRequest) { + case ATTACH_TO: + client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable); + break; + case HANDOVER_TO: + client.handOverSplashScreenView(r); + break; + } + } + + @Override + public void recycle() { + ObjectPool.recycle(this); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRequest); + dest.writeTypedObject(mSplashScreenViewParcelable, flags); + } + + private TransferSplashScreenViewStateItem() {} + private TransferSplashScreenViewStateItem(Parcel in) { + mRequest = in.readInt(); + mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR); + } + + /** Obtain an instance initialized with provided params. */ + public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state, + @Nullable SplashScreenViewParcelable parcelable) { + TransferSplashScreenViewStateItem instance = + ObjectPool.obtain(TransferSplashScreenViewStateItem.class); + if (instance == null) { + instance = new TransferSplashScreenViewStateItem(); + } + instance.mRequest = state; + instance.mSplashScreenViewParcelable = parcelable; + + return instance; + } + + public static final @NonNull Creator<TransferSplashScreenViewStateItem> CREATOR = + new Creator<TransferSplashScreenViewStateItem>() { + public TransferSplashScreenViewStateItem createFromParcel(Parcel in) { + return new TransferSplashScreenViewStateItem(in); + } + + public TransferSplashScreenViewStateItem[] newArray(int size) { + return new TransferSplashScreenViewStateItem[size]; + } + }; +} diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index 018863774184..f3ecbf6c08f3 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -740,6 +740,7 @@ public class ClipData implements Parcelable { mIcon = null; mItems = new ArrayList<Item>(); mItems.add(item); + mClipDescription.setIsStyledText(isStyledText()); } /** @@ -756,6 +757,7 @@ public class ClipData implements Parcelable { mIcon = null; mItems = new ArrayList<Item>(); mItems.add(item); + mClipDescription.setIsStyledText(isStyledText()); } /** @@ -914,6 +916,9 @@ public class ClipData implements Parcelable { throw new NullPointerException("item is null"); } mItems.add(item); + if (mItems.size() == 1) { + mClipDescription.setIsStyledText(isStyledText()); + } } /** @@ -1049,6 +1054,20 @@ public class ClipData implements Parcelable { } } + private boolean isStyledText() { + if (mItems.isEmpty()) { + return false; + } + final CharSequence text = mItems.get(0).getText(); + if (text instanceof Spanned) { + Spanned spanned = (Spanned) text; + if (TextUtils.hasStyleSpan(spanned)) { + return true; + } + } + return false; + } + @Override public String toString() { StringBuilder b = new StringBuilder(128); diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java index e3395e20947d..d48f83223d3e 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -120,6 +120,7 @@ public class ClipDescription implements Parcelable { private final ArrayList<String> mMimeTypes; private PersistableBundle mExtras; private long mTimeStamp; + private boolean mIsStyledText; /** * Create a new clip. @@ -325,6 +326,26 @@ public class ClipDescription implements Parcelable { } } + /** + * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e. + * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link + * android.text.style.ParagraphStyle ParagraphStyle}, or {@link + * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if + * there is no associated clip data. + */ + public boolean isStyledText() { + return mIsStyledText; + } + + /** + * Sets whether the associated {@link ClipData} contains styled text in its first item. This + * should be called when this description is associated with clip data or when the first item + * is added to the associated clip data. + */ + void setIsStyledText(boolean isStyledText) { + mIsStyledText = isStyledText; + } + @Override public String toString() { StringBuilder b = new StringBuilder(128); @@ -429,6 +450,7 @@ public class ClipDescription implements Parcelable { dest.writeStringList(mMimeTypes); dest.writePersistableBundle(mExtras); dest.writeLong(mTimeStamp); + dest.writeBoolean(mIsStyledText); } ClipDescription(Parcel in) { @@ -436,6 +458,7 @@ public class ClipDescription implements Parcelable { mMimeTypes = in.createStringArrayList(); mExtras = in.readPersistableBundle(); mTimeStamp = in.readLong(); + mIsStyledText = in.readBoolean(); } public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR = diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 85549d854c6d..ebe202b2a3fa 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -273,7 +273,7 @@ public class AppSearchShortcutInfo extends GenericDocument { text, 0, null, disabledMessage, 0, null, categoriesSet, intents, rank, extras, getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri, - disabledReason, persons, locusId); + disabledReason, persons, locusId, 0); } /** @hide */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 6cfcce3f7661..a3c3500f742f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3592,9 +3592,12 @@ public abstract class PackageManager { * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has * the requisite kernel support to support incremental delivery aka Incremental FileSystem. * - * @see IncrementalManager#isEnabled + * @see IncrementalManager#isFeatureEnabled * @hide + * + * @deprecated Use {@link #FEATURE_INCREMENTAL_DELIVERY_VERSION} instead. */ + @Deprecated @SystemApi @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_INCREMENTAL_DELIVERY = @@ -3602,6 +3605,20 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * feature not present - IncFs is not present on the device. + * 1 - IncFs v1, core features, no PerUid support. Optional in R. + * 2 - IncFs v2, PerUid support, fs-verity support. Required in S. + * + * @see IncrementalManager#isFeatureEnabled and IncrementalManager#isV2() + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = + "android.software.incremental_delivery_version"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device has tuner hardware to support tuner operations. * * <p>This feature implies that the device has the tuner HAL implementation. diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index a2e533af64a0..0e70a3e4e600 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -28,6 +28,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForStringSet; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Set; @@ -477,6 +480,8 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { */ public @Nullable CharSequence nonLocalizedDescription; + private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class); + /** * A {@link Set} of trusted signing certificate digests. If this permission has the {@link * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app @@ -688,6 +693,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { dest.writeInt(descriptionRes); dest.writeInt(requestRes); TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags); + sForStringSet.parcel(knownCerts, dest, parcelableFlags); } /** @hide */ @@ -753,5 +759,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { descriptionRes = source.readInt(); requestRes = source.readInt(); nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + knownCerts = sForStringSet.unparcel(source); } } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 522f4ca88519..ce0547f5d0f4 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -434,6 +434,8 @@ public final class ShortcutInfo implements Parcelable { private int mDisabledReason; + private int mStartingThemeResId; + private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -462,6 +464,7 @@ public final class ShortcutInfo implements Parcelable { mLocusId = b.mLocusId; updateTimestamp(); + mStartingThemeResId = b.mStartingThemeResId; } /** @@ -608,6 +611,7 @@ public final class ShortcutInfo implements Parcelable { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } + mStartingThemeResId = source.mStartingThemeResId; } /** @@ -931,6 +935,9 @@ public final class ShortcutInfo implements Parcelable { if (source.mLocusId != null) { mLocusId = source.mLocusId; } + if (source.mStartingThemeResId != 0) { + mStartingThemeResId = source.mStartingThemeResId; + } } /** @@ -1000,6 +1007,8 @@ public final class ShortcutInfo implements Parcelable { private LocusId mLocusId; + private int mStartingThemeResId; + /** * Old style constructor. * @hide @@ -1102,6 +1111,15 @@ public final class ShortcutInfo implements Parcelable { } /** + * Sets a theme resource id for the splash screen. + */ + @NonNull + public Builder setStartingTheme(int themeResId) { + mStartingThemeResId = themeResId; + return this; + } + + /** * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests * use it.) */ @@ -1420,6 +1438,14 @@ public final class ShortcutInfo implements Parcelable { return mIcon; } + /** + * Returns the theme resource id used for the splash screen. + * @hide + */ + public int getStartingThemeResId() { + return mStartingThemeResId; + } + /** @hide -- old signature, the internal code still uses it. */ @Nullable @Deprecated @@ -2138,6 +2164,7 @@ public final class ShortcutInfo implements Parcelable { mPersons = source.readParcelableArray(cl, Person.class); mLocusId = source.readParcelable(cl); mIconUri = source.readString8(); + mStartingThemeResId = source.readInt(); } @Override @@ -2189,6 +2216,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeParcelableArray(mPersons, flags); dest.writeParcelable(mLocusId, flags); dest.writeString8(mIconUri); + dest.writeInt(mStartingThemeResId); } public static final @NonNull Creator<ShortcutInfo> CREATOR = @@ -2345,6 +2373,12 @@ public final class ShortcutInfo implements Parcelable { sb.append("disabledReason="); sb.append(getDisabledReasonDebugString(mDisabledReason)); + if (mStartingThemeResId != 0) { + addIndentOrComma(sb, indent); + sb.append("SplashScreenThemeResId="); + sb.append(Integer.toHexString(mStartingThemeResId)); + } + addIndentOrComma(sb, indent); sb.append("categories="); @@ -2430,7 +2464,7 @@ public final class ShortcutInfo implements Parcelable { Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, long lastChangedTimestamp, int flags, int iconResId, String iconResName, String bitmapPath, String iconUri, - int disabledReason, Person[] persons, LocusId locusId) { + int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2459,5 +2493,6 @@ public final class ShortcutInfo implements Parcelable { mDisabledReason = disabledReason; mPersons = persons; mLocusId = locusId; + mStartingThemeResId = startingThemeResId; } } diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index c62767ee031b..233abf36131b 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -71,6 +71,13 @@ public abstract class ShortcutServiceInternal { public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); + /** + * Get the theme res ID of the starting window, it can be 0 if not specified. + */ + public abstract int getShortcutStartingThemeResId(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, + int userId); + public abstract ParcelFileDescriptor getShortcutIconFd(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java index 35bb33c84d56..37e0e87c548c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermission.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java @@ -21,16 +21,22 @@ import android.content.pm.PermissionInfo; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; +import com.android.internal.util.Parcelling.BuiltIn.ForStringSet; +import java.util.Locale; import java.util.Set; /** @hide */ public class ParsedPermission extends ParsedComponent { + private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class); + @Nullable String backgroundPermission; @Nullable @@ -89,6 +95,19 @@ public class ParsedPermission extends ParsedComponent { return knownCerts; } + protected void setKnownCert(String knownCert) { + // Convert the provided digest to upper case for consistent Set membership + // checks when verifying the signing certificate digests of requesting apps. + this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US)); + } + + protected void setKnownCerts(String[] knownCerts) { + this.knownCerts = new ArraySet<>(); + for (String knownCert : knownCerts) { + this.knownCerts.add(knownCert.toUpperCase(Locale.US)); + } + } + public int calculateFootprint() { int size = getName().length(); if (getNonLocalizedLabel() != null) { @@ -117,6 +136,7 @@ public class ParsedPermission extends ParsedComponent { dest.writeInt(this.protectionLevel); dest.writeBoolean(this.tree); dest.writeParcelable(this.parsedPermissionGroup, flags); + sForStringSet.parcel(knownCerts, dest, flags); } protected ParsedPermission(Parcel in) { @@ -129,6 +149,7 @@ public class ParsedPermission extends ParsedComponent { this.protectionLevel = in.readInt(); this.tree = in.readBoolean(); this.parsedPermissionGroup = in.readParcelable(boot); + this.knownCerts = sForStringSet.unparcel(in); } public static final Parcelable.Creator<ParsedPermission> CREATOR = diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java index a7cecbee8aec..8afa70ec6364 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java @@ -25,7 +25,6 @@ import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.util.ArraySet; import android.util.Slog; import com.android.internal.R; @@ -33,8 +32,6 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.Locale; -import java.util.Set; /** @hide */ public class ParsedPermissionUtils { @@ -103,17 +100,12 @@ public class ParsedPermissionUtils { if (resourceType.equals("array")) { final String[] knownCerts = res.getStringArray(knownCertsResource); if (knownCerts != null) { - // Convert the provided digest to upper case for consistent Set membership - // checks when verifying the signing certificate digests of requesting apps. - permission.knownCerts = new ArraySet<>(); - for (String knownCert : knownCerts) { - permission.knownCerts.add(knownCert.toUpperCase(Locale.US)); - } + permission.setKnownCerts(knownCerts); } } else { final String knownCert = res.getString(knownCertsResource); if (knownCert != null) { - permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US)); + permission.setKnownCert(knownCert); } } if (permission.knownCerts == null) { @@ -126,7 +118,7 @@ public class ParsedPermissionUtils { final String knownCert = sa.getString( R.styleable.AndroidManifestPermission_knownCerts); if (knownCert != null) { - permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US)); + permission.setKnownCert(knownCert); } } diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS index cde7b2ac1898..d302b0ae1ea8 100644 --- a/core/java/android/content/pm/permission/OWNERS +++ b/core/java/android/content/pm/permission/OWNERS @@ -3,7 +3,6 @@ toddke@android.com toddke@google.com patb@google.com -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com zhanghai@google.com diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index abf694f9742e..bbde8b103ef3 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -252,9 +252,10 @@ public class CompatibilityInfo implements Parcelable { } if (overrideScale != 1.0f) { - applicationDensity = DisplayMetrics.DENSITY_DEFAULT; applicationScale = overrideScale; applicationInvertedScale = 1.0f / overrideScale; + applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE + * applicationInvertedScale) + .5f); compatFlags |= HAS_OVERRIDE_SCALING; } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { applicationDensity = DisplayMetrics.DENSITY_DEVICE; @@ -519,10 +520,6 @@ public class CompatibilityInfo implements Parcelable { if (isScalingRequired()) { float invertedRatio = applicationInvertedScale; inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f); - inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f); - inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f); - inoutConfig.smallestScreenWidthDp = - (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f); inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio); inoutConfig.windowConfiguration.getBounds().scale(invertedRatio); final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds(); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 8fe71583912c..8451dedb6c37 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -1233,6 +1233,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { int sequenceId) { synchronized (mInterfaceLock) { if (mInternalRepeatingRequestEnabled) { + mRepeatingRequestImageReader.setOnImageAvailableListener( + new ImageLoopbackCallback(), mHandler); resumeInternalRepeatingRequest(true); } } @@ -1263,7 +1265,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mRequestUpdatedNeeded = false; resumeInternalRepeatingRequest(false); } else if (mInternalRepeatingRequestEnabled) { + mRepeatingRequestImageReader.setOnImageAvailableListener( + new ImageLoopbackCallback(), mHandler); resumeInternalRepeatingRequest(true); + } else { + mRepeatingRequestImageReader.setOnImageAvailableListener( + new ImageLoopbackCallback(), mHandler); } } diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index f175e7b00b7e..2d4b2ccd7514 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -42,6 +42,12 @@ public final class DeviceStateManager { */ public static final int INVALID_DEVICE_STATE = -1; + /** The minimum allowed device state identifier. */ + public static final int MINIMUM_DEVICE_STATE = 0; + + /** The maximum allowed device state identifier. */ + public static final int MAXIMUM_DEVICE_STATE = 255; + private final DeviceStateManagerGlobal mGlobal; /** @hide */ diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index f3da6a9d4a03..886a8c1fdae5 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -833,67 +833,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * @hide - */ - public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) { - switch (acquireInfo) { - case FACE_ACQUIRED_GOOD: - return null; - case FACE_ACQUIRED_INSUFFICIENT: - return context.getString(R.string.face_acquired_insufficient); - case FACE_ACQUIRED_TOO_BRIGHT: - return context.getString(R.string.face_acquired_too_bright); - case FACE_ACQUIRED_TOO_DARK: - return context.getString(R.string.face_acquired_too_dark); - case FACE_ACQUIRED_TOO_CLOSE: - return context.getString(R.string.face_acquired_too_close); - case FACE_ACQUIRED_TOO_FAR: - return context.getString(R.string.face_acquired_too_far); - case FACE_ACQUIRED_TOO_HIGH: - return context.getString(R.string.face_acquired_too_high); - case FACE_ACQUIRED_TOO_LOW: - return context.getString(R.string.face_acquired_too_low); - case FACE_ACQUIRED_TOO_RIGHT: - return context.getString(R.string.face_acquired_too_right); - case FACE_ACQUIRED_TOO_LEFT: - return context.getString(R.string.face_acquired_too_left); - case FACE_ACQUIRED_POOR_GAZE: - return context.getString(R.string.face_acquired_poor_gaze); - case FACE_ACQUIRED_NOT_DETECTED: - return context.getString(R.string.face_acquired_not_detected); - case FACE_ACQUIRED_TOO_MUCH_MOTION: - return context.getString(R.string.face_acquired_too_much_motion); - case FACE_ACQUIRED_RECALIBRATE: - return context.getString(R.string.face_acquired_recalibrate); - case FACE_ACQUIRED_TOO_DIFFERENT: - return context.getString(R.string.face_acquired_too_different); - case FACE_ACQUIRED_TOO_SIMILAR: - return context.getString(R.string.face_acquired_too_similar); - case FACE_ACQUIRED_PAN_TOO_EXTREME: - return context.getString(R.string.face_acquired_pan_too_extreme); - case FACE_ACQUIRED_TILT_TOO_EXTREME: - return context.getString(R.string.face_acquired_tilt_too_extreme); - case FACE_ACQUIRED_ROLL_TOO_EXTREME: - return context.getString(R.string.face_acquired_roll_too_extreme); - case FACE_ACQUIRED_FACE_OBSCURED: - return context.getString(R.string.face_acquired_obscured); - case FACE_ACQUIRED_START: - return null; - case FACE_ACQUIRED_SENSOR_DIRTY: - return context.getString(R.string.face_acquired_sensor_dirty); - case FACE_ACQUIRED_VENDOR: { - String[] msgArray = context.getResources().getStringArray( - R.array.face_acquired_vendor); - if (vendorCode < msgArray.length) { - return msgArray[vendorCode]; - } - } - } - Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode); - return null; - } - - /** * Used so BiometricPrompt can map the face ones onto existing public constants. * @hide */ @@ -1387,7 +1326,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan final int acquireInfo = frame.getData().getAcquiredInfo(); final int vendorCode = frame.getData().getVendorCode(); final int helpCode = getHelpCode(acquireInfo, vendorCode); - final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode); + final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode); mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); // Ensure that only non-null help messages are sent. @@ -1405,7 +1344,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan final int acquireInfo = frame.getData().getAcquiredInfo(); final int vendorCode = frame.getData().getVendorCode(); final int helpCode = getHelpCode(acquireInfo, vendorCode); - final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode); + final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode); mEnrollmentCallback.onEnrollmentHelp(helpCode, helpMessage); } } @@ -1415,4 +1354,124 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan ? vendorCode + FACE_ACQUIRED_VENDOR_BASE : acquireInfo; } + + /** + * @hide + */ + @Nullable + public static String getAuthHelpMessage(Context context, int acquireInfo, int vendorCode) { + switch (acquireInfo) { + // No help message is needed for a good capture. + case FACE_ACQUIRED_GOOD: + case FACE_ACQUIRED_START: + return null; + + // Consolidate positional feedback to reduce noise during authentication. + case FACE_ACQUIRED_NOT_DETECTED: + case FACE_ACQUIRED_TOO_CLOSE: + case FACE_ACQUIRED_TOO_FAR: + case FACE_ACQUIRED_TOO_HIGH: + case FACE_ACQUIRED_TOO_LOW: + case FACE_ACQUIRED_TOO_RIGHT: + case FACE_ACQUIRED_TOO_LEFT: + case FACE_ACQUIRED_POOR_GAZE: + case FACE_ACQUIRED_PAN_TOO_EXTREME: + case FACE_ACQUIRED_TILT_TOO_EXTREME: + case FACE_ACQUIRED_ROLL_TOO_EXTREME: + return context.getString(R.string.face_acquired_not_detected); + + // Provide more detailed feedback for other soft errors. + case FACE_ACQUIRED_INSUFFICIENT: + return context.getString(R.string.face_acquired_insufficient); + case FACE_ACQUIRED_TOO_BRIGHT: + return context.getString(R.string.face_acquired_too_bright); + case FACE_ACQUIRED_TOO_DARK: + return context.getString(R.string.face_acquired_too_dark); + case FACE_ACQUIRED_TOO_MUCH_MOTION: + return context.getString(R.string.face_acquired_too_much_motion); + case FACE_ACQUIRED_RECALIBRATE: + return context.getString(R.string.face_acquired_recalibrate); + case FACE_ACQUIRED_TOO_DIFFERENT: + return context.getString(R.string.face_acquired_too_different); + case FACE_ACQUIRED_TOO_SIMILAR: + return context.getString(R.string.face_acquired_too_similar); + case FACE_ACQUIRED_FACE_OBSCURED: + return context.getString(R.string.face_acquired_obscured); + case FACE_ACQUIRED_SENSOR_DIRTY: + return context.getString(R.string.face_acquired_sensor_dirty); + + // Find and return the appropriate vendor-specific message. + case FACE_ACQUIRED_VENDOR: { + String[] msgArray = context.getResources().getStringArray( + R.array.face_acquired_vendor); + if (vendorCode < msgArray.length) { + return msgArray[vendorCode]; + } + } + } + + Slog.w(TAG, "Unknown authentication acquired message: " + acquireInfo + ", " + vendorCode); + return null; + } + + /** + * @hide + */ + @Nullable + public static String getEnrollHelpMessage(Context context, int acquireInfo, int vendorCode) { + switch (acquireInfo) { + case FACE_ACQUIRED_GOOD: + case FACE_ACQUIRED_START: + return null; + case FACE_ACQUIRED_INSUFFICIENT: + return context.getString(R.string.face_acquired_insufficient); + case FACE_ACQUIRED_TOO_BRIGHT: + return context.getString(R.string.face_acquired_too_bright); + case FACE_ACQUIRED_TOO_DARK: + return context.getString(R.string.face_acquired_too_dark); + case FACE_ACQUIRED_TOO_CLOSE: + return context.getString(R.string.face_acquired_too_close); + case FACE_ACQUIRED_TOO_FAR: + return context.getString(R.string.face_acquired_too_far); + case FACE_ACQUIRED_TOO_HIGH: + return context.getString(R.string.face_acquired_too_high); + case FACE_ACQUIRED_TOO_LOW: + return context.getString(R.string.face_acquired_too_low); + case FACE_ACQUIRED_TOO_RIGHT: + return context.getString(R.string.face_acquired_too_right); + case FACE_ACQUIRED_TOO_LEFT: + return context.getString(R.string.face_acquired_too_left); + case FACE_ACQUIRED_POOR_GAZE: + return context.getString(R.string.face_acquired_poor_gaze); + case FACE_ACQUIRED_NOT_DETECTED: + return context.getString(R.string.face_acquired_not_detected); + case FACE_ACQUIRED_TOO_MUCH_MOTION: + return context.getString(R.string.face_acquired_too_much_motion); + case FACE_ACQUIRED_RECALIBRATE: + return context.getString(R.string.face_acquired_recalibrate); + case FACE_ACQUIRED_TOO_DIFFERENT: + return context.getString(R.string.face_acquired_too_different); + case FACE_ACQUIRED_TOO_SIMILAR: + return context.getString(R.string.face_acquired_too_similar); + case FACE_ACQUIRED_PAN_TOO_EXTREME: + return context.getString(R.string.face_acquired_pan_too_extreme); + case FACE_ACQUIRED_TILT_TOO_EXTREME: + return context.getString(R.string.face_acquired_tilt_too_extreme); + case FACE_ACQUIRED_ROLL_TOO_EXTREME: + return context.getString(R.string.face_acquired_roll_too_extreme); + case FACE_ACQUIRED_FACE_OBSCURED: + return context.getString(R.string.face_acquired_obscured); + case FACE_ACQUIRED_SENSOR_DIRTY: + return context.getString(R.string.face_acquired_sensor_dirty); + case FACE_ACQUIRED_VENDOR: { + String[] msgArray = context.getResources().getStringArray( + R.array.face_acquired_vendor); + if (vendorCode < msgArray.length) { + return msgArray[vendorCode]; + } + } + } + Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode); + return null; + } } diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS index 383321bc3d69..bd40409f71c6 100644 --- a/core/java/android/hardware/location/OWNERS +++ b/core/java/android/hardware/location/OWNERS @@ -4,3 +4,6 @@ mstogaitis@google.com wyattriley@google.com etn@google.com weiwa@google.com + +# ContextHub team +per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS index 8f2b39da4f63..8f5c2a025672 100644 --- a/core/java/android/hardware/usb/OWNERS +++ b/core/java/android/hardware/usb/OWNERS @@ -1,4 +1,3 @@ # Bug component: 175220 -moltmann@google.com badhri@google.com diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index d6774d47b49e..933256a3b475 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -58,6 +58,9 @@ interface IIpSecService in LinkAddress localAddr, in String callingPackage); + void setNetworkForTunnelInterface( + int tunnelResourceId, in Network underlyingNetwork, in String callingPackage); + void deleteTunnelInterface(int resourceId, in String callingPackage); IpSecTransformResponse createTransform( diff --git a/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl new file mode 100644 index 000000000000..7979afc54f90 --- /dev/null +++ b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl @@ -0,0 +1,23 @@ +/** + * + * 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.net; + +/** @hide */ +oneway interface IOnSetOemNetworkPreferenceListener { + void onComplete(); +} diff --git a/core/java/android/net/IVpnManager.aidl b/core/java/android/net/IVpnManager.aidl new file mode 100644 index 000000000000..271efe41a9ef --- /dev/null +++ b/core/java/android/net/IVpnManager.aidl @@ -0,0 +1,62 @@ +/** + * 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 android.net; + +import android.net.Network; + +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; + +/** + * Interface that manages VPNs. + */ +/** {@hide} */ +interface IVpnManager { + /** VpnService APIs */ + boolean prepareVpn(String oldPackage, String newPackage, int userId); + void setVpnPackageAuthorization(String packageName, int userId, int vpnType); + ParcelFileDescriptor establishVpn(in VpnConfig config); + boolean addVpnAddress(String address, int prefixLength); + boolean removeVpnAddress(String address, int prefixLength); + boolean setUnderlyingNetworksForVpn(in Network[] networks); + + /** VpnManager APIs */ + boolean provisionVpnProfile(in VpnProfile profile, String packageName); + void deleteVpnProfile(String packageName); + void startVpnProfile(String packageName); + void stopVpnProfile(String packageName); + + /** Always-on VPN APIs */ + boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); + boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, + in List<String> lockdownAllowlist); + String getAlwaysOnVpnPackage(int userId); + boolean isVpnLockdownEnabled(int userId); + List<String> getVpnLockdownAllowlist(int userId); + boolean isCallerCurrentAlwaysOnVpnApp(); + boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + + /** Legacy VPN APIs */ + void startLegacyVpn(in VpnProfile profile); + LegacyVpnInfo getLegacyVpnInfo(int userId); + boolean updateLockdownVpn(); + + /** General system APIs */ + VpnConfig getVpnConfig(int userId); + void factoryReset(); +} diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 70bca3019818..98acd98cc465 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -782,6 +782,42 @@ public final class IpSecManager { } } + /** + * Update the underlying network for this IpSecTunnelInterface. + * + * <p>This new underlying network will be used for all transforms applied AFTER this call is + * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to + * this tunnel interface, traffic will still use the old SA, and be routed on the old + * underlying network. + * + * <p>To migrate IPsec tunnel mode traffic, a caller should: + * + * <ol> + * <li>Update the IpSecTunnelInterface’s underlying network. + * <li>Apply {@link IpSecTransform}(s) with matching addresses to this + * IpSecTunnelInterface. + * </ol> + * + * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel. + * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise + * this method will throw an {@link IllegalArgumentException}. + */ + // TODO: b/169171001 Update the documentation when transform migration is supported. + // The purpose of making updating network and applying transforms separate is to leave open + // the possibility to support lossless migration procedures. To do that, Android platform + // will need to support multiple inbound tunnel mode transforms, just like it can support + // multiple transport mode transforms. + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException { + try { + mService.setNetworkForTunnelInterface( + mResourceId, underlyingNetwork, mOpPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service, @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork) diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 3e6237d99011..6353a25e745f 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -55,6 +56,7 @@ import java.util.concurrent.ConcurrentHashMap; * * @hide */ +@TestApi @SystemService(Context.NETWORK_POLICY_SERVICE) public class NetworkPolicyManager { @@ -125,6 +127,7 @@ public class NetworkPolicyManager { public static final int RULE_REJECT_ALL = 1 << 6; /** * Reject traffic on all networks for restricted networking mode. + * @hide */ public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10; @@ -351,6 +354,7 @@ public class NetworkPolicyManager { } /** @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setRestrictBackground(boolean restrictBackground) { try { @@ -361,6 +365,7 @@ public class NetworkPolicyManager { } /** @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean getRestrictBackground() { try { @@ -506,6 +511,8 @@ public class NetworkPolicyManager { /** * Get multipath preference for the given network. + * + * @hide */ public int getMultipathPreference(Network network) { try { @@ -624,7 +631,9 @@ public class NetworkPolicyManager { } /** @hide */ - public static String resolveNetworkId(WifiConfiguration config) { + @TestApi + @NonNull + public static String resolveNetworkId(@NonNull WifiConfiguration config) { return WifiInfo.sanitizeSsid(config.isPasspoint() ? config.providerFriendlyName : config.SSID); } diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java index 5e56164cc82c..b4034556f66e 100644 --- a/core/java/android/net/OemNetworkPreferences.java +++ b/core/java/android/net/OemNetworkPreferences.java @@ -18,6 +18,7 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcelable; @@ -29,11 +30,12 @@ import java.util.Map; import java.util.Objects; /** @hide */ +@SystemApi public final class OemNetworkPreferences implements Parcelable { /** - * Use default behavior requesting networks. Equivalent to not setting any preference at all. + * Default in case this value is not set. Using it will result in an error. */ - public static final int OEM_NETWORK_PREFERENCE_DEFAULT = 0; + public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; /** * If an unmetered network is available, use it. @@ -45,17 +47,17 @@ public final class OemNetworkPreferences implements Parcelable { /** * If an unmetered network is available, use it. * Otherwise, if a network with the OEM_PAID capability is available, use it. - * Otherwise, the app doesn't get a network. + * Otherwise, the app doesn't get a default network. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; /** - * Prefer only NET_CAPABILITY_OEM_PAID networks. + * Use only NET_CAPABILITY_OEM_PAID networks. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; /** - * Prefer only NET_CAPABILITY_OEM_PRIVATE networks. + * Use only NET_CAPABILITY_OEM_PRIVATE networks. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; @@ -95,8 +97,6 @@ public final class OemNetworkPreferences implements Parcelable { /** * Builder used to create {@link OemNetworkPreferences} objects. Specify the preferred Network * to package name mappings. - * - * @hide */ public static final class Builder { private final Bundle mNetworkMappings; @@ -135,7 +135,7 @@ public final class OemNetworkPreferences implements Parcelable { * @return The builder to facilitate chaining. */ @NonNull - public Builder removeNetworkPreference(@NonNull final String packageName) { + public Builder clearNetworkPreference(@NonNull final String packageName) { Objects.requireNonNull(packageName); mNetworkMappings.remove(packageName); return this; @@ -160,7 +160,7 @@ public final class OemNetworkPreferences implements Parcelable { /** @hide */ @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = { - OEM_NETWORK_PREFERENCE_DEFAULT, + OEM_NETWORK_PREFERENCE_UNINITIALIZED, OEM_NETWORK_PREFERENCE_OEM_PAID, OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK, OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY, @@ -174,12 +174,14 @@ public final class OemNetworkPreferences implements Parcelable { * * @param value int value of OemNetworkPreference * @return string version of OemNetworkPreference + * + * @hide */ @NonNull public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) { switch (value) { - case OEM_NETWORK_PREFERENCE_DEFAULT: - return "OEM_NETWORK_PREFERENCE_DEFAULT"; + case OEM_NETWORK_PREFERENCE_UNINITIALIZED: + return "OEM_NETWORK_PREFERENCE_UNINITIALIZED"; case OEM_NETWORK_PREFERENCE_OEM_PAID: return "OEM_NETWORK_PREFERENCE_OEM_PAID"; case OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK: diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 1e30283a9e6c..f472ed4381d1 100644 --- a/packages/Connectivity/framework/src/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; import android.content.ComponentName; @@ -37,6 +38,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.GeneralSecurityException; +import java.util.List; /** * This class provides an interface for apps to manage platform VPN profiles @@ -76,13 +78,19 @@ public class VpnManager { @Deprecated public static final int TYPE_VPN_LEGACY = 3; + /** + * Channel for VPN notifications. + * @hide + */ + public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; + /** @hide */ @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY}) @Retention(RetentionPolicy.SOURCE) public @interface VpnType {} @NonNull private final Context mContext; - @NonNull private final IConnectivityManager mService; + @NonNull private final IVpnManager mService; private static Intent getIntentForConfirmation() { final Intent intent = new Intent(); @@ -101,9 +109,9 @@ public class VpnManager { * * @hide */ - public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) { mContext = checkNotNull(ctx, "missing Context"); - mService = checkNotNull(service, "missing IConnectivityManager"); + mService = checkNotNull(service, "missing IVpnManager"); } /** @@ -195,6 +203,19 @@ public class VpnManager { } /** + * Resets all VPN settings back to factory defaults. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void factoryReset() { + try { + mService.factoryReset(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Prepare for a VPN application. * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. @@ -240,6 +261,108 @@ public class VpnManager { } /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + * <ul> + * <li>target {@link VERSION_CODES#N API 24} or above, and + * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} + * meta-data field. + * </ul> + * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + try { + return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + * <p>The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @param lockdownAllowlist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be allowed is installed or + * uninstalled. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownAllowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public String getAlwaysOnVpnPackageForUser(int userId) { + try { + return mService.getAlwaysOnVpnPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List<String> getVpnLockdownAllowlist(int userId) { + try { + return mService.getVpnLockdownAllowlist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the legacy VPN information for the specified user ID. * @hide */ diff --git a/packages/Connectivity/framework/src/android/net/VpnService.java b/core/java/android/net/VpnService.java index 8e90a119fe21..e43b0b6fa635 100644 --- a/packages/Connectivity/framework/src/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -170,12 +170,11 @@ public class VpnService extends Service { "android.net.VpnService.SUPPORTS_ALWAYS_ON"; /** - * Use IConnectivityManager since those methods are hidden and not - * available in ConnectivityManager. + * Use IVpnManager since those methods are hidden and not available in VpnManager. */ - private static IConnectivityManager getService() { - return IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + private static IVpnManager getService() { + return IVpnManager.Stub.asInterface( + ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE)); } /** @@ -226,15 +225,15 @@ public class VpnService extends Service { @SystemApi @RequiresPermission(android.Manifest.permission.CONTROL_VPN) public static void prepareAndAuthorize(Context context) { - IConnectivityManager cm = getService(); + IVpnManager vm = getService(); String packageName = context.getPackageName(); try { // Only prepare if we're not already prepared. int userId = context.getUserId(); - if (!cm.prepareVpn(packageName, null, userId)) { - cm.prepareVpn(null, packageName, userId); + if (!vm.prepareVpn(packageName, null, userId)) { + vm.prepareVpn(null, packageName, userId); } - cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); + vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); } catch (RemoteException e) { // ignore } diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 72a6e16c7df5..bf229e0b24df 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -47,7 +47,9 @@ public abstract class BatteryConsumer { POWER_COMPONENT_SYSTEM_SERVICES, POWER_COMPONENT_SENSORS, POWER_COMPONENT_GNSS, + POWER_COMPONENT_WAKELOCK, POWER_COMPONENT_SCREEN, + POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS, }) @Retention(RetentionPolicy.SOURCE) public static @interface PowerComponent { @@ -64,9 +66,14 @@ public abstract class BatteryConsumer { public static final int POWER_COMPONENT_MOBILE_RADIO = 8; public static final int POWER_COMPONENT_SENSORS = 9; public static final int POWER_COMPONENT_GNSS = 10; + public static final int POWER_COMPONENT_WAKELOCK = 12; public static final int POWER_COMPONENT_SCREEN = 13; + // Power that is re-attributed to other battery consumers. For example, for System Server + // this represents the power attributed to apps requesting system services. + // The value should be negative or zero. + public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 14; - public static final int POWER_COMPONENT_COUNT = 14; + public static final int POWER_COMPONENT_COUNT = 15; public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000; public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999; @@ -87,6 +94,7 @@ public abstract class BatteryConsumer { TIME_COMPONENT_MOBILE_RADIO, TIME_COMPONENT_SENSORS, TIME_COMPONENT_GNSS, + TIME_COMPONENT_WAKELOCK, TIME_COMPONENT_SCREEN, }) @Retention(RetentionPolicy.SOURCE) @@ -104,6 +112,7 @@ public abstract class BatteryConsumer { public static final int TIME_COMPONENT_MOBILE_RADIO = 8; public static final int TIME_COMPONENT_SENSORS = 9; public static final int TIME_COMPONENT_GNSS = 10; + public static final int TIME_COMPONENT_WAKELOCK = 12; public static final int TIME_COMPONENT_SCREEN = 13; public static final int TIME_COMPONENT_COUNT = 14; @@ -236,5 +245,13 @@ public abstract class BatteryConsumer { componentUsageTimeMillis); return (T) this; } + + /** + * Returns the total power accumulated by this builder so far. It may change + * by the time the {@code build()} method is called. + */ + public double getTotalPower() { + return mPowerComponentsBuilder.getTotalPower(); + } } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index cc86a604c194..01a89017ab6c 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -994,6 +994,19 @@ public abstract class BatteryStats implements Parcelable { */ public abstract long getScreenOnEnergy(); + /** + * Returns the energies used by this uid for each + * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer + * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). + * + * @return energies (in microjoules) used since boot for each (custom) energy consumer of + * type OTHER, indexed by their ordinal. Returns null if no energy reporting is + * supported. + * + * {@hide} + */ + public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules(); + public static abstract class Sensor { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @@ -2511,6 +2524,19 @@ public abstract class BatteryStats implements Parcelable { */ public abstract long getScreenDozeEnergy(); + /** + * Returns the energies used for each + * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer + * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). + * + * @return energies (in microjoules) used since boot for each (custom) energy consumer of + * type OTHER, indexed by their ordinal. Returns null if no energy reporting is + * supported. + * + * {@hide} + */ + public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules(); + public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] { new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"), new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"), @@ -5268,6 +5294,15 @@ public abstract class BatteryStats implements Parcelable { pw.print(" flash="); printmAh(pw, bs.flashlightPowerMah); } + if (bs.customMeasuredPowerMah != null) { + for (int idx = 0; idx < bs.customMeasuredPowerMah.length; idx++) { + final double customPowerMah = bs.customMeasuredPowerMah[idx]; + if (customPowerMah != 0) { + pw.print(" custom[" + idx + "]="); + printmAh(pw, customPowerMah); + } + } + } pw.print(" )"); } diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 6a76da2cc13d..e3b13f4f9f17 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -1902,7 +1902,8 @@ public final class Debug * Retrieves the PSS memory used by the process as given by the smaps. Optionally supply a long * array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and * Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and - * another array to also retrieve the separate memtrack size. + * another array to also retrieve the separate memtrack sizes (up to 4 values in order): the + * total memtrack reported size, memtrack graphics, memtrack gl and memtrack other. * * @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone). * @hide @@ -2565,6 +2566,14 @@ public final class Debug public static native long getDmabufTotalExportedKb(); /** + * Return total memory size in kilobytes for DMA-BUFs exported from the DMA-BUF + * heaps frameworks or -1 in the case of an error. + * + * @hide + */ + public static native long getDmabufHeapTotalExportedKb(); + + /** * Return memory size in kilobytes allocated for ION heaps or -1 if * /sys/kernel/ion/total_heaps_kb could not be read. * @@ -2589,6 +2598,13 @@ public final class Debug public static native long getIonPoolsSizeKb(); /** + * Return GPU DMA buffer usage in kB or -1 on error. + * + * @hide + */ + public static native long getGpuDmaBufUsageKb(); + + /** * Return DMA-BUF memory mapped by processes in kB. * Notes: * * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process. diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl index 52f0ce1f054f..4d160da22ff8 100644 --- a/core/java/android/os/ISystemConfig.aidl +++ b/core/java/android/os/ISystemConfig.aidl @@ -35,4 +35,9 @@ interface ISystemConfig { * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries */ Map getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries(); + + /** + * @see SystemConfigManager#getSystemPermissionUids + */ + int[] getSystemPermissionUids(String permissionName); } diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index dac1edea7d3e..a04047df4af1 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -1,4 +1,6 @@ # Haptics +per-file CombinedVibrationEffect.aidl = michaelwr@google.com +per-file CombinedVibrationEffect.java = michaelwr@google.com per-file ExternalVibration.aidl = michaelwr@google.com per-file ExternalVibration.java = michaelwr@google.com per-file IExternalVibrationController.aidl = michaelwr@google.com @@ -6,9 +8,11 @@ per-file IExternalVibratorService.aidl = michaelwr@google.com per-file IVibratorManagerService.aidl = michaelwr@google.com per-file NullVibrator.java = michaelwr@google.com per-file SystemVibrator.java = michaelwr@google.com +per-file SystemVibratorManager.java = michaelwr@google.com per-file VibrationEffect.aidl = michaelwr@google.com per-file VibrationEffect.java = michaelwr@google.com per-file Vibrator.java = michaelwr@google.com +per-file VibratorManager.java = michaelwr@google.com # PowerManager per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index 1337d558e439..ac2328504f74 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -38,11 +38,7 @@ class PowerComponents { mCustomPowerComponentCount = builder.mCustomPowerComponentCount; mPowerComponents = builder.mPowerComponents; mTimeComponents = builder.mTimeComponents; - double totalPower = 0; - for (int i = mPowerComponents.length - 1; i >= 0; i--) { - totalPower += mPowerComponents[i]; - } - mTotalPowerConsumed = totalPower; + mTotalPowerConsumed = builder.getTotalPower(); } PowerComponents(@NonNull Parcel source) { @@ -264,6 +260,18 @@ class PowerComponents { } /** + * Returns the total power accumulated by this builder so far. It may change + * by the time the {@code build()} method is called. + */ + public double getTotalPower() { + double totalPower = 0; + for (int i = mPowerComponents.length - 1; i >= 0; i--) { + totalPower += mPowerComponents[i]; + } + return totalPower; + } + + /** * Creates a read-only object out of the Builder values. */ @NonNull diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 8068c872c4bb..54d2df865c39 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -970,6 +970,16 @@ public class Process { throws IllegalArgumentException, SecurityException; /** + * + * Create a new process group in the cgroup uid/pid hierarchy + * + * @return <0 in case of error + * + * @hide + */ + public static final native int createProcessGroup(int uid, int pid); + + /** * On some devices, the foreground process may have one or more CPU * cores exclusively reserved for it. This method can be used to * retrieve which cores that are (if any), so the calling process diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java index 3f0632be90d1..9bfa8adc8571 100644 --- a/core/java/android/os/SystemConfigManager.java +++ b/core/java/android/os/SystemConfigManager.java @@ -111,4 +111,22 @@ public class SystemConfigManager { return Collections.emptyMap(); } } + + /** + * Get uids which have been granted given permission in system configuration. + * + * The uids and assigning permissions are defined on data/etc/platform.xml + * + * @param permissionName The target permission. + * @return The uids have been granted given permission in system configuration. + */ + @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) + @NonNull + public int[] getSystemPermissionUids(@NonNull String permissionName) { + try { + return mInterface.getSystemPermissionUids(permissionName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 0ff68fc582d8..05899947c3df 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -241,6 +241,13 @@ public final class IncrementalManager { } /** + * Checks if device supports V2 calls (e.g. PerUid). + */ + public static boolean isV2Available() { + return nativeIsV2Available(); + } + + /** * Checks if Incremental installations are allowed. * A developer can disable Incremental installations by setting the property. */ @@ -439,6 +446,7 @@ public final class IncrementalManager { /* Native methods */ private static native boolean nativeIsEnabled(); + private static native boolean nativeIsV2Available(); private static native boolean nativeIsIncrementalPath(@NonNull String path); private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path); } diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index d09f351bdfd1..b32346848a69 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,6 +1,5 @@ # Bug component: 137825 -moltmann@google.com evanseverson@google.com ntmyren@google.com zhanghai@google.com diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 85e9fdb9a9d1..0e35ef98f1b7 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -693,13 +693,14 @@ public class PermissionUsageHelper { for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) { OpUsage usage = rawUsages.get(usageNum); + // If this attribution is a proxy, remove it + if (toRemoveProxies.contains(usage.toPackageAttr())) { + continue; + } + // If this attribution has a special attribution, do not remove it if (specialAttributions.contains(usage.toPackageAttr())) { deDuped.add(usage); - } - - // If this attribution is a proxy, remove it - if (toRemoveProxies.contains(usage.toPackageAttr())) { continue; } diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS index d09f351bdfd1..b32346848a69 100644 --- a/core/java/android/permissionpresenterservice/OWNERS +++ b/core/java/android/permissionpresenterservice/OWNERS @@ -1,6 +1,5 @@ # Bug component: 137825 -moltmann@google.com evanseverson@google.com ntmyren@google.com zhanghai@google.com diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/print/OWNERS +++ b/core/java/android/print/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/print/pdf/OWNERS +++ b/core/java/android/print/pdf/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/printservice/OWNERS +++ b/core/java/android/printservice/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/printservice/recommendation/OWNERS +++ b/core/java/android/printservice/recommendation/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/android/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java index 6fdfaabb009b..9de75cac159a 100644 --- a/core/java/android/service/notification/NotificationListenerFilter.java +++ b/core/java/android/service/notification/NotificationListenerFilter.java @@ -20,6 +20,7 @@ import static android.service.notification.NotificationListenerService.FLAG_FILT import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT; +import android.content.pm.VersionedPackage; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; @@ -31,7 +32,8 @@ import android.util.ArraySet; */ public class NotificationListenerFilter implements Parcelable { private int mAllowedNotificationTypes; - private ArraySet<String> mDisallowedPackages; + // VersionedPackage is holding the pkg name and pkg uid + private ArraySet<VersionedPackage> mDisallowedPackages; public NotificationListenerFilter() { mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS @@ -41,7 +43,7 @@ public class NotificationListenerFilter implements Parcelable { mDisallowedPackages = new ArraySet<>(); } - public NotificationListenerFilter(int types, ArraySet<String> pkgs) { + public NotificationListenerFilter(int types, ArraySet<VersionedPackage> pkgs) { mAllowedNotificationTypes = types; mDisallowedPackages = pkgs; } @@ -51,7 +53,8 @@ public class NotificationListenerFilter implements Parcelable { */ protected NotificationListenerFilter(Parcel in) { mAllowedNotificationTypes = in.readInt(); - mDisallowedPackages = (ArraySet<String>) in.readArraySet(String.class.getClassLoader()); + mDisallowedPackages = (ArraySet<VersionedPackage>) in.readArraySet( + VersionedPackage.class.getClassLoader()); } @Override @@ -77,7 +80,7 @@ public class NotificationListenerFilter implements Parcelable { return (mAllowedNotificationTypes & type) != 0; } - public boolean isPackageAllowed(String pkg) { + public boolean isPackageAllowed(VersionedPackage pkg) { return !mDisallowedPackages.contains(pkg); } @@ -85,7 +88,7 @@ public class NotificationListenerFilter implements Parcelable { return mAllowedNotificationTypes; } - public ArraySet<String> getDisallowedPackages() { + public ArraySet<VersionedPackage> getDisallowedPackages() { return mDisallowedPackages; } @@ -93,10 +96,18 @@ public class NotificationListenerFilter implements Parcelable { mAllowedNotificationTypes = types; } - public void setDisallowedPackages(ArraySet<String> pkgs) { + public void setDisallowedPackages(ArraySet<VersionedPackage> pkgs) { mDisallowedPackages = pkgs; } + public void removePackage(VersionedPackage pkg) { + mDisallowedPackages.remove(pkg); + } + + public void addPackage(VersionedPackage pkg) { + mDisallowedPackages.add(pkg); + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 64cddc35d2bb..f66f85b9e8cc 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -80,6 +80,10 @@ import java.util.Objects; * <intent-filter> * <action android:name="android.service.notification.NotificationListenerService" /> * </intent-filter> + * <meta-data + * android:name="android.service.notification.default_filter_types" + * android:value="1,2"> + * </meta-data> * </service></pre> * * <p>The service should wait for the {@link #onListenerConnected()} event @@ -103,6 +107,21 @@ public abstract class NotificationListenerService extends Service { private final String TAG = getClass().getSimpleName(); /** + * The name of the {@code meta-data} tag containing a comma separated list of default + * integer notification types that should be provided to this listener. See + * {@link #FLAG_FILTER_TYPE_ONGOING}, + * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING), + * and {@link #FLAG_FILTER_TYPE_SILENT}. + * <p>This value will only be read if the app has not previously specified a default type list, + * and if the user has not overridden the allowed types.</p> + * <p>An absent value means 'allow all types'. + * A present but empty value means 'allow no types'.</p> + * + */ + public static final String META_DATA_DEFAULT_FILTER_TYPES + = "android.service.notification.default_filter_types"; + + /** * {@link #getCurrentInterruptionFilter() Interruption filter} constant - * Normal interruption filter. */ @@ -254,23 +273,19 @@ public abstract class NotificationListenerService extends Service { /** * A flag value indicating that this notification listener can see conversation type * notifications. - * @hide */ public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; /** * A flag value indicating that this notification listener can see altering type notifications. - * @hide */ public static final int FLAG_FILTER_TYPE_ALERTING = 2; /** * A flag value indicating that this notification listener can see silent type notifications. - * @hide */ public static final int FLAG_FILTER_TYPE_SILENT = 4; /** * A flag value indicating that this notification listener can see important * ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications. - * @hide */ public static final int FLAG_FILTER_TYPE_ONGOING = 8; diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java index a750b689ee02..87add57f383d 100644 --- a/core/java/android/service/storage/ExternalStorageService.java +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -95,6 +95,21 @@ public abstract class ExternalStorageService extends Service { public static final String EXTRA_ERROR = "android.service.storage.extra.error"; + /** + * {@link Bundle} key for a package name {@link String} value. + * + * {@hide} + */ + public static final String EXTRA_PACKAGE_NAME = "android.service.storage.extra.package_name"; + + /** + * {@link Bundle} key for a {@link Long} value. + * + * {@hide} + */ + public static final String EXTRA_ANR_TIMEOUT_MS = + "android.service.storage.extra.anr_timeout_ms"; + /** @hide */ @IntDef(flag = true, prefix = {"FLAG_SESSION_"}, value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE}) @@ -162,6 +177,15 @@ public abstract class ExternalStorageService extends Service { throw new UnsupportedOperationException("onFreeCacheRequested not implemented"); } + /** + * Called when {@code packageName} is about to ANR + * + * @return ANR dialog delay in milliseconds + */ + public long onGetAnrDelayMillis(@NonNull String packageName, int uid) { + throw new UnsupportedOperationException("onGetAnrDelayMillis not implemented"); + } + @Override @NonNull public final IBinder onBind(@NonNull Intent intent) { @@ -222,6 +246,19 @@ public abstract class ExternalStorageService extends Service { }); } + @Override + public void getAnrDelayMillis(String packageName, int uid, RemoteCallback callback) + throws RemoteException { + mHandler.post(() -> { + try { + long timeoutMs = onGetAnrDelayMillis(packageName, uid); + sendTimeoutResult(packageName, timeoutMs, null /* throwable */, callback); + } catch (Throwable t) { + sendTimeoutResult(packageName, 0 /* timeoutMs */, t, callback); + } + }); + } + private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) { Bundle bundle = new Bundle(); bundle.putString(EXTRA_SESSION_ID, sessionId); @@ -230,5 +267,16 @@ public abstract class ExternalStorageService extends Service { } callback.sendResult(bundle); } + + private void sendTimeoutResult(String packageName, long timeoutMs, Throwable throwable, + RemoteCallback callback) { + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_PACKAGE_NAME, packageName); + bundle.putLong(EXTRA_ANR_TIMEOUT_MS, timeoutMs); + if (throwable != null) { + bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable)); + } + callback.sendResult(bundle); + } } } diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl index d06671b3fb9f..2e0bd86c3d7d 100644 --- a/core/java/android/service/storage/IExternalStorageService.aidl +++ b/core/java/android/service/storage/IExternalStorageService.aidl @@ -32,4 +32,5 @@ oneway interface IExternalStorageService in RemoteCallback callback); void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes, in RemoteCallback callback); + void getAnrDelayMillis(String packageName, int uid, in RemoteCallback callback); }
\ No newline at end of file diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 70ec2d42b59b..6eba83fee48c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1083,7 +1083,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (creating) { updateOpaqueFlag(); - mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot); + final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]"; + if (mUseBlastAdapter) { + createBlastSurfaceControls(viewRoot, name); + } else { + mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name); + } } else if (mSurfaceControl == null) { return; } @@ -1220,53 +1225,77 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * out, the old surface can be persevered until the new one has drawn by keeping the reference * of the old SurfaceControl alive. */ - private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) { - final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]"; - - SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession) + private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot, String name) { + final SurfaceControl previousSurfaceControl = mSurfaceControl; + mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) .setName(name) .setLocalOwnerView(this) .setParent(viewRoot.getBoundsLayer()) - .setCallsite("SurfaceView.updateSurface"); + .setCallsite("SurfaceView.updateSurface") + .setBufferSize(mSurfaceWidth, mSurfaceHeight) + .setFlags(mSurfaceFlags) + .setFormat(mFormat) + .build(); + mBackgroundControl = createBackgroundControl(name); + return previousSurfaceControl; + } - final SurfaceControl previousSurfaceControl; - if (mUseBlastAdapter) { - mSurfaceControl = builder + private SurfaceControl createBackgroundControl(String name) { + return new SurfaceControl.Builder(mSurfaceSession) + .setName("Background for " + name) + .setLocalOwnerView(this) + .setOpaque(true) + .setColorLayer() + .setParent(mSurfaceControl) + .setCallsite("SurfaceView.updateSurface") + .build(); + } + + // We don't recreate the surface controls but only recreate the adapter. Since the blast layer + // is still alive, the old buffers will continue to be presented until replaced by buffers from + // the new adapter. This means we do not need to track the old surface control and destroy it + // after the client has drawn to avoid any flickers. + private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) { + if (mSurfaceControl == null) { + mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) + .setName(name) + .setLocalOwnerView(this) + .setParent(viewRoot.getBoundsLayer()) + .setCallsite("SurfaceView.updateSurface") .setContainerLayer() .build(); - previousSurfaceControl = mBlastSurfaceControl; + } + + if (mBlastSurfaceControl == null) { mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) .setName(name + "(BLAST)") .setLocalOwnerView(this) - .setBufferSize(mSurfaceWidth, mSurfaceHeight) .setParent(mSurfaceControl) .setFlags(mSurfaceFlags) .setHidden(false) .setBLASTLayer() .setCallsite("SurfaceView.updateSurface") .build(); - mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth, - mSurfaceHeight, mFormat, true /* TODO */); } else { - previousSurfaceControl = mSurfaceControl; - mSurfaceControl = builder - .setBufferSize(mSurfaceWidth, mSurfaceHeight) - .setFlags(mSurfaceFlags) - .setFormat(mFormat) - .build(); - mBlastSurfaceControl = null; - mBlastBufferQueue = null; + // update blast layer + mTmpTransaction + .setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0) + .setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0) + .show(mBlastSurfaceControl) + .apply(); } - mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession) - .setName("Background for " + name) - .setLocalOwnerView(this) - .setOpaque(true) - .setColorLayer() - .setParent(mSurfaceControl) - .setCallsite("SurfaceView.updateSurface") - .build(); - return previousSurfaceControl; + if (mBackgroundControl == null) { + mBackgroundControl = createBackgroundControl(name); + } + + // Always recreate the IGBP for compatibility. This can be optimized in the future but + // the behavior change will need to be gated by SDK version. + if (mBlastBufferQueue != null) { + mBlastBufferQueue.destroy(); + } + mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth, + mSurfaceHeight, mFormat, true /* TODO */); } private void onDrawFinished() { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 755ae31c077b..228ee3c76b14 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1898,11 +1898,12 @@ public final class ViewRootImpl implements ViewParent, } private void setBoundsLayerCrop(Transaction t) { - // mWinFrame is already adjusted for surface insets. So offset it and use it as - // the cropping bounds. - mTempBoundsRect.set(mWinFrame); - mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left, - mWindowAttributes.surfaceInsets.top); + // Adjust of insets and update the bounds layer so child surfaces do not draw into + // the surface inset region. + mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y); + mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left, + mWindowAttributes.surfaceInsets.top, + mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom); t.setWindowCrop(mBoundsLayer, mTempBoundsRect); } @@ -2835,8 +2836,7 @@ public final class ViewRootImpl implements ViewParent, mScroller.abortAnimation(); } // Our surface is gone - if (mAttachInfo.mThreadedRenderer != null && - mAttachInfo.mThreadedRenderer.isEnabled()) { + if (isHardwareEnabled()) { mAttachInfo.mThreadedRenderer.destroy(); } } else if ((surfaceReplaced @@ -3063,8 +3063,10 @@ public final class ViewRootImpl implements ViewParent, // via the WM relayout code path. We probably eventually // want to synchronize transparent region hint changes // with draws. - mTransaction.setTransparentRegionHint(getSurfaceControl(), - mTransparentRegion).apply(); + SurfaceControl sc = getSurfaceControl(); + if (sc.isValid()) { + mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply(); + } } } @@ -3920,8 +3922,15 @@ public final class ViewRootImpl implements ViewParent, }; } + /** + * @hide + */ + public boolean isHardwareEnabled() { + return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled(); + } + private boolean addFrameCompleteCallbackIfNeeded() { - if (mAttachInfo.mThreadedRenderer == null || !mAttachInfo.mThreadedRenderer.isEnabled()) { + if (!isHardwareEnabled()) { return false; } @@ -4265,7 +4274,7 @@ public final class ViewRootImpl implements ViewParent, boolean useAsyncReport = false; if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { - if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { + if (isHardwareEnabled()) { // If accessibility focus moved, always invalidate the root. boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested; mInvalidateRootRequested = false; diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index af18293398da..4ecdd78f5a42 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1703,6 +1703,30 @@ public abstract class Window { public abstract void setBackgroundDrawable(Drawable drawable); /** + * Blurs the screen behind the window within the bounds of the window. + * + * The density of the blur is set by the blur radius. The radius defines the size + * of the neighbouring area, from which pixels will be averaged to form the final + * color for each pixel. The operation approximates a Gaussian blur. + * A radius of 0 means no blur. The higher the radius, the denser the blur. + * + * The window background drawable is drawn on top of the blurred region. The blur + * region bounds and rounded corners will mimic those of the background drawable. + * + * For the blur region to be visible, the window has to be translucent. See + * {@link android.R.styleable#Window_windowIsTranslucent}. + * + * Note the difference with {@link android.view.WindowManager.LayoutParams#blurBehindRadius}, + * which blurs the whole screen behind the window. Background blur blurs the screen behind + * only within the bounds of the window. + * + * @param blurRadius The blur radius to use for window background blur in pixels + * + * @see android.R.styleable#Window_windowBackgroundBlurRadius + */ + public void setBackgroundBlurRadius(int blurRadius) {} + + /** * Set the value for a drawable feature of this window, from a resource * identifier. You must have called requestFeature(featureId) before * calling this function. diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 61ff36c09cb9..1b62266c12e2 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -18,15 +18,21 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; import android.graphics.BlendMode; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.RecordingCanvas; import android.graphics.Rect; +import android.graphics.RenderEffect; +import android.graphics.RenderNode; import android.os.Build; +import android.util.AttributeSet; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; @@ -111,11 +117,14 @@ public class EdgeEffect { private float mGlowAlpha; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private float mGlowScaleY; + private float mDistance; private float mGlowAlphaStart; private float mGlowAlphaFinish; private float mGlowScaleYStart; private float mGlowScaleYFinish; + private float mDistanceStart; + private float mDistanceFinish; private long mStartTime; private float mDuration; @@ -144,15 +153,26 @@ public class EdgeEffect { private float mDisplacement = 0.5f; private float mTargetDisplacement = 0.5f; private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW; + private Matrix mTmpMatrix = null; + private float[] mTmpPoints = null; /** * Construct a new EdgeEffect with a theme appropriate for the provided context. * @param context Context used to provide theming and resource information for the EdgeEffect */ public EdgeEffect(Context context) { + this(context, null); + } + + /** + * Construct a new EdgeEffect with a theme appropriate for the provided context. + * @param context Context used to provide theming and resource information for the EdgeEffect + * @param attrs The attributes of the XML tag that is inflating the view + */ + public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) { mPaint.setAntiAlias(true); final TypedArray a = context.obtainStyledAttributes( - com.android.internal.R.styleable.EdgeEffect); + attrs, com.android.internal.R.styleable.EdgeEffect); final int themeColor = a.getColor( com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666); mEdgeEffectType = a.getInt( @@ -236,11 +256,18 @@ public class EdgeEffect { public void onPull(float deltaDistance, float displacement) { final long now = AnimationUtils.currentAnimationTimeMillis(); mTargetDisplacement = displacement; - if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { + if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration + && mEdgeEffectType == TYPE_GLOW) { return; } if (mState != STATE_PULL) { - mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); + if (mEdgeEffectType == TYPE_STRETCH) { + // Restore the mPullDistance to the fraction it is currently showing -- we want + // to "catch" the current stretch value. + mPullDistance = mDistance; + } else { + mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); + } } mState = STATE_PULL; @@ -248,6 +275,7 @@ public class EdgeEffect { mDuration = PULL_TIME; mPullDistance += deltaDistance; + mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance); final float absdd = Math.abs(deltaDistance); mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, @@ -267,6 +295,65 @@ public class EdgeEffect { } /** + * A view should call this when content is pulled away from an edge by the user. + * This will update the state of the current visual effect and its associated animation. + * The host view should always {@link android.view.View#invalidate()} after this + * and draw the results accordingly. This works similarly to {@link #onPull(float, float)}, + * but returns the amount of <code>deltaDistance</code> that has been consumed. If the + * {@link #getDistance()} is currently 0 and <code>deltaDistance</code> is negative, this + * function will return 0 and the drawn value will remain unchanged. + * + * This method can be used to reverse the effect from a pull or absorb and partially consume + * some of a motion: + * + * <pre class="prettyprint"> + * if (deltaY < 0) { + * float consumed = edgeEffect.onPullDistance(deltaY / getHeight(), x / getWidth()); + * deltaY -= consumed * getHeight(); + * if (edgeEffect.getDistance() == 0f) edgeEffect.onRelease(); + * } + * </pre> + * + * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to + * 1.f (full length of the view) or negative values to express change + * back toward the edge reached to initiate the effect. + * @param displacement The displacement from the starting side of the effect of the point + * initiating the pull. In the case of touch this is the finger position. + * Values may be from 0-1. + * @return The amount of <code>deltaDistance</code> that was consumed, a number between + * 0 and <code>deltaDistance</code>. + */ + public float onPullDistance(float deltaDistance, float displacement) { + float finalDistance = Math.max(0f, deltaDistance + mDistance); + float delta = finalDistance - mDistance; + if (delta == 0f && mDistance == 0f) { + return 0f; // No pull, don't do anything. + } + + if (mState != STATE_PULL && mState != STATE_PULL_DECAY && mEdgeEffectType == TYPE_GLOW) { + // Catch the edge glow in the middle of an animation. + mPullDistance = mDistance; + mState = STATE_PULL; + } + onPull(delta, displacement); + return delta; + } + + /** + * Returns the pull distance needed to be released to remove the showing effect. + * It is determined by the {@link #onPull(float, float)} <code>deltaDistance</code> and + * any animating values, including from {@link #onAbsorb(int)} and {@link #onRelease()}. + * + * This can be used in conjunction with {@link #onPullDistance(float, float)} to + * release the currently showing effect. + * + * @return The pull distance that must be released to remove the showing effect. + */ + public float getDistance() { + return mDistance; + } + + /** * Call when the object is released after being pulled. * This will begin the "decay" phase of the effect. After calling this method * the host view should {@link android.view.View#invalidate()} and thereby @@ -282,9 +369,11 @@ public class EdgeEffect { mState = STATE_RECEDE; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; + mDistanceStart = mDistance; mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; + mDistanceFinish = 0.f; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; @@ -311,7 +400,7 @@ public class EdgeEffect { // nearly invisible. mGlowAlphaStart = GLOW_ALPHA_START; mGlowScaleYStart = Math.max(mGlowScaleY, 0.f); - + mDistanceStart = mDistance; // Growth for the size of the glow should be quadratic to properly // respond @@ -322,6 +411,9 @@ public class EdgeEffect { mGlowAlphaFinish = Math.max( mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); mTargetDisplacement = 0.5f; + + // Use glow values to estimate the absorption for stretch distance. + mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish); } /** @@ -396,33 +488,59 @@ public class EdgeEffect { * Draw into the provided canvas. Assumes that the canvas has been rotated * accordingly and the size has been set. The effect will be drawn the full * width of X=0 to X=width, beginning from Y=0 and extending to some factor < - * 1.f of height. + * 1.f of height. The {@link #TYPE_STRETCH} effect will only be visible on a + * hardware canvas, e.g. {@link RenderNode#beginRecording()}. * * @param canvas Canvas to draw into * @return true if drawing should continue beyond this frame to continue the * animation */ public boolean draw(Canvas canvas) { - update(); - - final int count = canvas.save(); - - final float centerX = mBounds.centerX(); - final float centerY = mBounds.height() - mRadius; - - canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0); - - final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; - float translateX = mBounds.width() * displacement / 2; - - canvas.clipRect(mBounds); - canvas.translate(translateX, 0); - mPaint.setAlpha((int) (0xff * mGlowAlpha)); - canvas.drawCircle(centerX, centerY, mRadius, mPaint); - canvas.restoreToCount(count); + if (mEdgeEffectType == TYPE_GLOW) { + update(); + final int count = canvas.save(); + + final float centerX = mBounds.centerX(); + final float centerY = mBounds.height() - mRadius; + + canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0); + + final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; + float translateX = mBounds.width() * displacement / 2; + + canvas.clipRect(mBounds); + canvas.translate(translateX, 0); + mPaint.setAlpha((int) (0xff * mGlowAlpha)); + canvas.drawCircle(centerX, centerY, mRadius, mPaint); + canvas.restoreToCount(count); + } else if (canvas instanceof RecordingCanvas) { + if (mState != STATE_PULL) { + update(); + } + RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; + if (mTmpMatrix == null) { + mTmpMatrix = new Matrix(); + mTmpPoints = new float[4]; + } + //noinspection deprecation + recordingCanvas.getMatrix(mTmpMatrix); + mTmpPoints[0] = mBounds.width() * mDisplacement; + mTmpPoints[1] = mDistance * mBounds.height(); + mTmpPoints[2] = mTmpPoints[0]; + mTmpPoints[3] = 0; + mTmpMatrix.mapPoints(mTmpPoints); + float x = mTmpPoints[0] - mTmpPoints[2]; + float y = mTmpPoints[1] - mTmpPoints[3]; + + RenderNode renderNode = recordingCanvas.mNode; + + // TODO: use stretchy RenderEffect and use internal API when it is ready + // TODO: wrap existing RenderEffect + renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y)); + } boolean oneLastFrame = false; - if (mState == STATE_RECEDE && mGlowScaleY == 0) { + if (mState == STATE_RECEDE && mDistance == 0) { mState = STATE_IDLE; oneLastFrame = true; } @@ -447,6 +565,7 @@ public class EdgeEffect { mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; + mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp; mDisplacement = (mDisplacement + mTargetDisplacement) / 2; if (t >= 1.f - EPSILON) { @@ -458,10 +577,12 @@ public class EdgeEffect { mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; + mDistanceStart = mDistance; // After absorb, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; + mDistanceFinish = 0.f; break; case STATE_PULL: mState = STATE_PULL_DECAY; @@ -470,10 +591,12 @@ public class EdgeEffect { mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; + mDistanceStart = mDistance; // After pull, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; + mDistanceFinish = 0.f; break; case STATE_PULL_DECAY: mState = STATE_RECEDE; @@ -484,4 +607,20 @@ public class EdgeEffect { } } } + + /** + * @return The estimated pull distance as calculated from mGlowScaleY. + */ + private float calculateDistanceFromGlowValues(float scale, float alpha) { + if (scale >= 1f) { + // It should asymptotically approach 1, but not reach there. + // Here, we're just choosing a value that is large. + return 1f; + } + if (scale > 0f) { + float v = 1f / 0.7f / (mGlowScaleY - 1f); + return v * v / mBounds.height(); + } + return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR; + } } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index 794b642135d0..d59a415469b6 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -69,9 +69,7 @@ public class SpellChecker implements SpellCheckerSessionListener { private final TextView mTextView; SpellCheckerSession mSpellCheckerSession; - // We assume that the sentence level spell check will always provide better results than words. - // Although word SC has a sequential option. - private boolean mIsSentenceSpellCheckSupported; + final int mCookie; // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated @@ -134,7 +132,6 @@ public class SpellChecker implements SpellCheckerSessionListener { | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR | SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS); - mIsSentenceSpellCheckSupported = true; } // Restore SpellCheckSpans in pool @@ -318,13 +315,11 @@ public class SpellChecker implements SpellCheckerSessionListener { && WordIterator.isMidWordPunctuation( mCurrentLocale, Character.codePointBefore(editable, end + 1))) { isEditing = false; - } else if (mIsSentenceSpellCheckSupported) { + } else { // Allow the overlap of the cursor and the first boundary of the spell check span // no to skip the spell check of the following word because the // following word will never be spell-checked even if the user finishes composing isEditing = selectionEnd <= start || selectionStart > end; - } else { - isEditing = selectionEnd < start || selectionStart > end; } if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) { spellCheckSpan.setSpellCheckInProgress(true); @@ -346,13 +341,8 @@ public class SpellChecker implements SpellCheckerSessionListener { textInfos = textInfosCopy; } - if (mIsSentenceSpellCheckSupported) { - mSpellCheckerSession.getSentenceSuggestions( - textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE); - } else { - mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE, - false /* TODO Set sequentialWords to true for initial spell check */); - } + mSpellCheckerSession.getSentenceSuggestions( + textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE); } } @@ -381,32 +371,30 @@ public class SpellChecker implements SpellCheckerSessionListener { editable, suggestionsInfo, spellCheckSpan, offset, length); } else { // Valid word -- isInDictionary || !looksLikeTypo - if (mIsSentenceSpellCheckSupported) { - // Allow the spell checker to remove existing misspelled span by - // overwriting the span over the same place - final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); - final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan); - final int start; - final int end; - if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) { - start = spellCheckSpanStart + offset; - end = start + length; - } else { - start = spellCheckSpanStart; - end = spellCheckSpanEnd; - } - if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart - && end > start) { - final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end)); - final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key); - if (tempSuggestionSpan != null) { - if (DBG) { - Log.i(TAG, "Remove existing misspelled span. " - + editable.subSequence(start, end)); - } - editable.removeSpan(tempSuggestionSpan); - mSuggestionSpanCache.remove(key); + // Allow the spell checker to remove existing misspelled span by + // overwriting the span over the same place + final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); + final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan); + final int start; + final int end; + if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) { + start = spellCheckSpanStart + offset; + end = start + length; + } else { + start = spellCheckSpanStart; + end = spellCheckSpanEnd; + } + if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart + && end > start) { + final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end)); + final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key); + if (tempSuggestionSpan != null) { + if (DBG) { + Log.i(TAG, "Remove existing misspelled span. " + + editable.subSequence(start, end)); } + editable.removeSpan(tempSuggestionSpan); + mSuggestionSpanCache.remove(key); } } } @@ -531,20 +519,16 @@ public class SpellChecker implements SpellCheckerSessionListener { } SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, flags); - // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface - // to share the logic of word level spell checker and sentence level spell checker - if (mIsSentenceSpellCheckSupported) { - final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end)); - final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key); - if (tempSuggestionSpan != null) { - if (DBG) { - Log.i(TAG, "Cached span on the same position is cleard. " - + editable.subSequence(start, end)); - } - editable.removeSpan(tempSuggestionSpan); + final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end)); + final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key); + if (tempSuggestionSpan != null) { + if (DBG) { + Log.i(TAG, "Cached span on the same position is cleard. " + + editable.subSequence(start, end)); } - mSuggestionSpanCache.put(key, suggestionSpan); + editable.removeSpan(tempSuggestionSpan); } + mSuggestionSpanCache.put(key, suggestionSpan); editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mTextView.invalidateRegion(start, end, false /* No cursor involved */); @@ -599,15 +583,8 @@ public class SpellChecker implements SpellCheckerSessionListener { public void parse() { Editable editable = (Editable) mTextView.getText(); // Iterate over the newly added text and schedule new SpellCheckSpans - final int start; - if (mIsSentenceSpellCheckSupported) { - // TODO: Find the start position of the sentence. - // Set span with the context - start = Math.max( - 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH); - } else { - start = editable.getSpanStart(mRange); - } + final int start = Math.max( + 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH); final int end = editable.getSpanEnd(mRange); @@ -633,155 +610,80 @@ public class SpellChecker implements SpellCheckerSessionListener { return; } - // We need to expand by one character because we want to include the spans that - // end/start at position start/end respectively. - SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1, - SpellCheckSpan.class); - SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1, - SuggestionSpan.class); - - int wordCount = 0; boolean scheduleOtherSpellCheck = false; - if (mIsSentenceSpellCheckSupported) { - if (wordIteratorWindowEnd < end) { - if (DBG) { - Log.i(TAG, "schedule other spell check."); - } - // Several batches needed on that region. Cut after last previous word - scheduleOtherSpellCheck = true; - } - int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd); - boolean correct = spellCheckEnd != BreakIterator.DONE; - if (correct) { - spellCheckEnd = mWordIterator.getEnd(spellCheckEnd); - correct = spellCheckEnd != BreakIterator.DONE; + if (wordIteratorWindowEnd < end) { + if (DBG) { + Log.i(TAG, "schedule other spell check."); } - if (!correct) { - if (DBG) { - Log.i(TAG, "Incorrect range span."); - } - stop(); - return; + // Several batches needed on that region. Cut after last previous word + scheduleOtherSpellCheck = true; + } + int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd); + boolean correct = spellCheckEnd != BreakIterator.DONE; + if (correct) { + spellCheckEnd = mWordIterator.getEnd(spellCheckEnd); + correct = spellCheckEnd != BreakIterator.DONE; + } + if (!correct) { + if (DBG) { + Log.i(TAG, "Incorrect range span."); } - do { - // TODO: Find the start position of the sentence. - int spellCheckStart = wordStart; - boolean createSpellCheckSpan = true; - // Cancel or merge overlapped spell check spans - for (int i = 0; i < mLength; ++i) { - final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i]; - if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) { - continue; - } - final int spanStart = editable.getSpanStart(spellCheckSpan); - final int spanEnd = editable.getSpanEnd(spellCheckSpan); - if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) { - // No need to merge - continue; - } - if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) { - // There is a completely overlapped spell check span - // skip this span - createSpellCheckSpan = false; - if (DBG) { - Log.i(TAG, "The range is overrapped. Skip spell check."); - } - break; - } - // This spellCheckSpan is replaced by the one we are creating - editable.removeSpan(spellCheckSpan); - spellCheckStart = Math.min(spanStart, spellCheckStart); - spellCheckEnd = Math.max(spanEnd, spellCheckEnd); - } - - if (DBG) { - Log.d(TAG, "addSpellCheckSpan: " - + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart - + ", next = " + scheduleOtherSpellCheck + "\n" - + editable.subSequence(spellCheckStart, spellCheckEnd)); + stop(); + return; + } + do { + // TODO: Find the start position of the sentence. + int spellCheckStart = wordStart; + boolean createSpellCheckSpan = true; + // Cancel or merge overlapped spell check spans + for (int i = 0; i < mLength; ++i) { + final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i]; + if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) { + continue; } - - // Stop spell checking when there are no characters in the range. - if (spellCheckEnd < start) { - break; + final int spanStart = editable.getSpanStart(spellCheckSpan); + final int spanEnd = editable.getSpanEnd(spellCheckSpan); + if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) { + // No need to merge + continue; } - if (spellCheckEnd <= spellCheckStart) { - Log.w(TAG, "Trying to spellcheck invalid region, from " - + start + " to " + end); + if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) { + // There is a completely overlapped spell check span + // skip this span + createSpellCheckSpan = false; + if (DBG) { + Log.i(TAG, "The range is overrapped. Skip spell check."); + } break; } - if (createSpellCheckSpan) { - addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd); - } - } while (false); - wordStart = spellCheckEnd; - } else { - while (wordStart <= end) { - if (wordEnd >= start && wordEnd > wordStart) { - if (wordCount >= MAX_NUMBER_OF_WORDS) { - scheduleOtherSpellCheck = true; - break; - } - // A new word has been created across the interval boundaries with this - // edit. The previous spans (that ended on start / started on end) are - // not valid anymore and must be removed. - if (wordStart < start && wordEnd > start) { - removeSpansAt(editable, start, spellCheckSpans); - removeSpansAt(editable, start, suggestionSpans); - } - - if (wordStart < end && wordEnd > end) { - removeSpansAt(editable, end, spellCheckSpans); - removeSpansAt(editable, end, suggestionSpans); - } - - // Do not create new boundary spans if they already exist - boolean createSpellCheckSpan = true; - if (wordEnd == start) { - for (int i = 0; i < spellCheckSpans.length; i++) { - final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]); - if (spanEnd == start) { - createSpellCheckSpan = false; - break; - } - } - } - - if (wordStart == end) { - for (int i = 0; i < spellCheckSpans.length; i++) { - final int spanStart = editable.getSpanStart(spellCheckSpans[i]); - if (spanStart == end) { - createSpellCheckSpan = false; - break; - } - } - } + // This spellCheckSpan is replaced by the one we are creating + editable.removeSpan(spellCheckSpan); + spellCheckStart = Math.min(spanStart, spellCheckStart); + spellCheckEnd = Math.max(spanEnd, spellCheckEnd); + } - if (createSpellCheckSpan) { - addSpellCheckSpan(editable, wordStart, wordEnd); - } - wordCount++; - } + if (DBG) { + Log.d(TAG, "addSpellCheckSpan: " + + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart + + ", next = " + scheduleOtherSpellCheck + "\n" + + editable.subSequence(spellCheckStart, spellCheckEnd)); + } - // iterate word by word - int originalWordEnd = wordEnd; - wordEnd = mWordIterator.following(wordEnd); - if ((wordIteratorWindowEnd < end) && - (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) { - wordIteratorWindowEnd = - Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL); - mWordIterator.setCharSequence( - editable, originalWordEnd, wordIteratorWindowEnd); - wordEnd = mWordIterator.following(originalWordEnd); - } - if (wordEnd == BreakIterator.DONE) break; - wordStart = mWordIterator.getBeginning(wordEnd); - if (wordStart == BreakIterator.DONE) { - break; - } + // Stop spell checking when there are no characters in the range. + if (spellCheckEnd < start) { + break; } - } + if (spellCheckEnd <= spellCheckStart) { + Log.w(TAG, "Trying to spellcheck invalid region, from " + + start + " to " + end); + break; + } + if (createSpellCheckSpan) { + addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd); + } + } while (false); + wordStart = spellCheckEnd; if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) { // Update range span: start new spell check from last wordStart diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fe37c5350511..0f2089a5463f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12940,17 +12940,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); - final ClipDescription description = clipData.getDescription(); + final ClipDescription description = + getClipboardManagerForUser().getPrimaryClipDescription(); final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); - final CharSequence text = clipData.getItemAt(0).getText(); - if (isPlainType && (text instanceof Spanned)) { - Spanned spanned = (Spanned) text; - if (TextUtils.hasStyleSpan(spanned)) { - return true; - } - } - return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); + return (isPlainType && description.isStyledText()) + || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); } boolean canProcessText() { diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 88b2257a55b1..8f541d0bd194 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -42,6 +42,11 @@ oneway interface ITaskOrganizer { void removeStartingWindow(int taskId); /** + * Called when the Task want to copy the splash screen. + */ + void copySplashScreenView(int taskId); + + /** * A callback when the Task is available for the registered organizer. The client is responsible * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially * be hidden so it is up to the organizer to show this task. diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java new file mode 100644 index 000000000000..4b88a9bc39de --- /dev/null +++ b/core/java/android/window/SplashScreen.java @@ -0,0 +1,188 @@ +/* + * 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.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityThread; +import android.content.Context; +import android.os.IBinder; +import android.util.Singleton; +import android.util.Slog; + +import java.util.ArrayList; + +/** + * The interface that apps use to talk to the splash screen. + * <p> + * Each splash screen instance is bound to a particular {@link Activity}. + * To obtain a {@link SplashScreen} for an Activity, use + * <code>Activity.getSplashScreen()</code> to get the SplashScreen.</p> + */ +public interface SplashScreen { + /** + * <p>Specifies whether an {@link Activity} wants to handle the splash screen animation on its + * own. Normally the splash screen will show on screen before the content of the activity has + * been drawn, and disappear when the activity is showing on the screen. With this listener set, + * the activity will receive {@link OnExitAnimationListener#onSplashScreenExit} callback if + * splash screen is showed, then the activity can create its own exit animation based on the + * SplashScreenView.</p> + * + * <p> Note that this method must be called before splash screen leave, so it only takes effect + * during or before {@link Activity#onResume}.</p> + * + * @param listener the listener for receive the splash screen with + * + * @see OnExitAnimationListener#onSplashScreenExit(SplashScreenView) + */ + @SuppressLint("ExecutorRegistration") + void setOnExitAnimationListener(@Nullable SplashScreen.OnExitAnimationListener listener); + + /** + * Listens for the splash screen exit event. + */ + interface OnExitAnimationListener { + /** + * When receiving this callback, the {@link SplashScreenView} object will be drawing on top + * of the activity. The {@link SplashScreenView} represents the splash screen view + * object, developer can make an exit animation based on this view.</p> + * + * <p>If {@link SplashScreenView#remove} is not called after 5000ms, the method will be + * automatically called and the splash screen removed.</p> + * + * <p>This method is never invoked if your activity sets + * {@link #setOnExitAnimationListener} to <code>null</code>.. + * + * @param view The view object which on top of this Activity. + * @see #setOnExitAnimationListener + */ + void onSplashScreenExit(@NonNull SplashScreenView view); + } + + /** + * @hide + */ + class SplashScreenImpl implements SplashScreen { + private OnExitAnimationListener mExitAnimationListener; + private final IBinder mActivityToken; + private final SplashScreenManagerGlobal mGlobal; + + public SplashScreenImpl(Context context) { + mActivityToken = context.getActivityToken(); + mGlobal = SplashScreenManagerGlobal.getInstance(); + } + + @Override + public void setOnExitAnimationListener( + @Nullable SplashScreen.OnExitAnimationListener listener) { + if (mActivityToken == null) { + // This is not an activity. + return; + } + synchronized (mGlobal.mGlobalLock) { + mExitAnimationListener = listener; + if (listener != null) { + mGlobal.addImpl(this); + } else { + mGlobal.removeImpl(this); + } + } + } + } + + /** + * This class is only used internally to manage the activities for this process. + * + * @hide + */ + class SplashScreenManagerGlobal { + private static final String TAG = SplashScreen.class.getSimpleName(); + private final Object mGlobalLock = new Object(); + private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>(); + + private SplashScreenManagerGlobal() { + ActivityThread.currentActivityThread().registerSplashScreenManager(this); + } + + public static SplashScreenManagerGlobal getInstance() { + return sInstance.get(); + } + + private static final Singleton<SplashScreenManagerGlobal> sInstance = + new Singleton<SplashScreenManagerGlobal>() { + @Override + protected SplashScreenManagerGlobal create() { + return new SplashScreenManagerGlobal(); + } + }; + + private void addImpl(SplashScreenImpl impl) { + synchronized (mGlobalLock) { + mImpls.add(impl); + } + } + + private void removeImpl(SplashScreenImpl impl) { + synchronized (mGlobalLock) { + mImpls.remove(impl); + } + } + + private SplashScreenImpl findImpl(IBinder token) { + synchronized (mGlobalLock) { + for (SplashScreenImpl impl : mImpls) { + if (impl.mActivityToken == token) { + return impl; + } + } + } + return null; + } + + public void tokenDestroyed(IBinder token) { + synchronized (mGlobalLock) { + final SplashScreenImpl impl = findImpl(token); + if (impl != null) { + removeImpl(impl); + } + } + } + + public void dispatchOnExitAnimation(IBinder token, SplashScreenView view) { + synchronized (mGlobalLock) { + final SplashScreenImpl impl = findImpl(token); + if (impl == null) { + return; + } + if (impl.mExitAnimationListener == null) { + Slog.e(TAG, "cannot dispatch onExitAnimation to listener " + token); + return; + } + impl.mExitAnimationListener.onSplashScreenExit(view); + } + } + + public boolean containsExitListener(IBinder token) { + synchronized (mGlobalLock) { + final SplashScreenImpl impl = findImpl(token); + return impl != null && impl.mExitAnimationListener != null; + } + } + } +} diff --git a/core/java/android/window/SplashScreenView.aidl b/core/java/android/window/SplashScreenView.aidl new file mode 100644 index 000000000000..cc7ac1edce80 --- /dev/null +++ b/core/java/android/window/SplashScreenView.aidl @@ -0,0 +1,20 @@ +/* + * 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 android.window; + +/** @hide */ +parcelable SplashScreenView.SplashScreenViewParcelable; diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java new file mode 100644 index 000000000000..35ccfca101d3 --- /dev/null +++ b/core/java/android/window/SplashScreenView.java @@ -0,0 +1,510 @@ +/* + * 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 android.window; + +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.android.internal.R; +import com.android.internal.policy.DecorView; + +/** + * <p>The view which allows an activity to customize its splash screen exit animation.</p> + * + * <p>Activities will receive this view as a parameter of + * {@link SplashScreen.OnExitAnimationListener#onSplashScreenExit} if + * they set {@link SplashScreen#setOnExitAnimationListener}. + * When this callback is called, this view will be on top of the activity.</p> + * + * <p>This view is composed of a view containing the splashscreen icon (see + * windowSplashscreenAnimatedIcon) and a background. + * Developers can use {@link #getIconView} to get this view and replace the drawable or + * add animation to it. The background of this view is filled with a single color, which can be + * edited during the animation by {@link View#setBackground} or {@link View#setBackgroundColor}.</p> + * + * @see SplashScreen + */ +public final class SplashScreenView extends FrameLayout { + private static final String TAG = SplashScreenView.class.getSimpleName(); + private static final boolean DEBUG = false; + + private boolean mNotCopyable; + private int mInitBackgroundColor; + private View mIconView; + private Bitmap mParceledIconBitmap; + private View mBrandingImageView; + private Bitmap mParceledBrandingBitmap; + private long mIconAnimationDuration; + private long mIconAnimationStart; + + private Animatable mAnimatableIcon; + private ValueAnimator mAnimator; + + // cache original window and status + private Window mWindow; + private boolean mDrawBarBackground; + private int mStatusBarColor; + private int mNavigationBarColor; + + /** + * Internal builder to create a SplashScreenWindowView object. + * @hide + */ + public static class Builder { + private final Context mContext; + private int mIconSize; + private @ColorInt int mBackgroundColor; + private Bitmap mParceledIconBitmap; + private Drawable mIconDrawable; + private int mBrandingImageWidth; + private int mBrandingImageHeight; + private Drawable mBrandingDrawable; + private Bitmap mParceledBrandingBitmap; + private long mIconAnimationStart; + private long mIconAnimationDuration; + + public Builder(@NonNull Context context) { + mContext = context; + } + + /** + * When create from {@link SplashScreenViewParcelable}, all the materials were be settled so + * you do not need to call other set methods. + */ + public Builder createFromParcel(SplashScreenViewParcelable parcelable) { + mIconSize = parcelable.getIconSize(); + mBackgroundColor = parcelable.getBackgroundColor(); + if (parcelable.mIconBitmap != null) { + mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap); + mParceledIconBitmap = parcelable.mIconBitmap; + } + if (parcelable.mBrandingBitmap != null) { + setBrandingDrawable(new BitmapDrawable(mContext.getResources(), + parcelable.mBrandingBitmap), parcelable.mBrandingWidth, + parcelable.mBrandingHeight); + mParceledBrandingBitmap = parcelable.mBrandingBitmap; + } + mIconAnimationStart = parcelable.mIconAnimationStart; + mIconAnimationDuration = parcelable.mIconAnimationDuration; + return this; + } + + /** + * Set the rectangle size for the center view. + */ + public Builder setIconSize(int iconSize) { + mIconSize = iconSize; + return this; + } + + /** + * Set the background color for the view. + */ + public Builder setBackgroundColor(@ColorInt int backgroundColor) { + mBackgroundColor = backgroundColor; + return this; + } + + /** + * Set the Drawable object to fill the center view. + */ + public Builder setCenterViewDrawable(Drawable drawable) { + mIconDrawable = drawable; + return this; + } + + /** + * Set the animation duration if icon is animatable. + */ + public Builder setAnimationDuration(int duration) { + mIconAnimationDuration = duration; + return this; + } + + /** + * Set the Drawable object and size for the branding view. + */ + public Builder setBrandingDrawable(Drawable branding, int width, int height) { + mBrandingDrawable = branding; + mBrandingImageWidth = width; + mBrandingImageHeight = height; + return this; + } + + /** + * Create SplashScreenWindowView object from materials. + */ + public SplashScreenView build() { + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); + final SplashScreenView view = (SplashScreenView) + layoutInflater.inflate(R.layout.splash_screen_view, null, false); + view.mInitBackgroundColor = mBackgroundColor; + view.setBackgroundColor(mBackgroundColor); + view.mIconView = view.findViewById(R.id.splashscreen_icon_view); + view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view); + // center icon + if (mIconSize != 0) { + final ViewGroup.LayoutParams params = view.mIconView.getLayoutParams(); + params.width = mIconSize; + params.height = mIconSize; + view.mIconView.setLayoutParams(params); + } + if (mIconDrawable != null) { + view.mIconView.setBackground(mIconDrawable); + view.initIconAnimation(mIconDrawable, mIconAnimationDuration); + } + view.mIconAnimationStart = mIconAnimationStart; + view.mIconAnimationDuration = mIconAnimationDuration; + if (mParceledIconBitmap != null) { + view.mParceledIconBitmap = mParceledIconBitmap; + } + // branding image + if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) { + final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams(); + params.width = mBrandingImageWidth; + params.height = mBrandingImageHeight; + view.mBrandingImageView.setLayoutParams(params); + } + if (mBrandingDrawable != null) { + view.mBrandingImageView.setBackground(mBrandingDrawable); + } + if (mParceledBrandingBitmap != null) { + view.mParceledBrandingBitmap = mParceledBrandingBitmap; + } + if (DEBUG) { + Log.d(TAG, " build " + view + " Icon: view: " + view.mIconView + " drawable: " + + mIconDrawable + " size: " + mIconSize + "\n Branding: view: " + + view.mBrandingImageView + " drawable: " + mBrandingDrawable + + " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight); + } + return view; + } + } + + /** @hide */ + public SplashScreenView(Context context) { + super(context); + } + + /** @hide */ + public SplashScreenView(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + /** + * Declared this view is not copyable. + * @hide + */ + public void setNotCopyable() { + mNotCopyable = true; + } + + /** + * Whether this view is copyable. + * @hide + */ + public boolean isCopyable() { + return !mNotCopyable; + } + + /** + * Returns the duration of the icon animation if icon is animatable. + * + * @see android.R.attr#windowSplashScreenAnimatedIcon + * @see android.R.attr#windowSplashScreenAnimationDuration + */ + public long getIconAnimationDurationMillis() { + return mIconAnimationDuration; + } + + /** + * If the replaced icon is animatable, return the animation start time in millisecond based on + * system. The start time is set using {@link SystemClock#uptimeMillis()}. + */ + public long getIconAnimationStartMillis() { + return mIconAnimationStart; + } + + void initIconAnimation(Drawable iconDrawable, long duration) { + if (iconDrawable instanceof Animatable) { + mAnimatableIcon = (Animatable) iconDrawable; + mAnimator = ValueAnimator.ofInt(0, 1); + mAnimator.setDuration(duration); + mAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mIconAnimationStart = SystemClock.uptimeMillis(); + mAnimatableIcon.start(); + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimatableIcon.stop(); + } + + @Override + public void onAnimationCancel(Animator animation) { + mAnimatableIcon.stop(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + // do not repeat + mAnimatableIcon.stop(); + } + }); + } + } + + /** + * @hide + */ + public void startIntroAnimation() { + if (mAnimatableIcon != null) { + mAnimator.start(); + } + } + + /** + * <p>Remove this view and release its resource. </p> + * <p><strong>Do not</strong> invoke this method from a drawing method + * ({@link #onDraw(android.graphics.Canvas)} for instance).</p> + */ + public void remove() { + setVisibility(GONE); + if (mParceledIconBitmap != null) { + mIconView.setBackground(null); + mParceledIconBitmap.recycle(); + mParceledIconBitmap = null; + } + if (mParceledBrandingBitmap != null) { + mBrandingImageView.setBackground(null); + mParceledBrandingBitmap.recycle(); + mParceledBrandingBitmap = null; + } + if (mWindow != null) { + final DecorView decorView = (DecorView) mWindow.peekDecorView(); + if (DEBUG) { + Log.d(TAG, "remove starting view"); + } + if (decorView != null) { + decorView.removeView(this); + } + restoreSystemUIColors(); + mWindow = null; + } + } + + /** + * Cache the root window. + * @hide + */ + public void cacheRootWindow(Window window) { + mWindow = window; + } + + /** + * Called after SplashScreenView has added on the root window. + * @hide + */ + public void makeSystemUIColorsTransparent() { + if (mWindow != null) { + final WindowManager.LayoutParams attr = mWindow.getAttributes(); + mDrawBarBackground = (attr.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; + mWindow.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + mStatusBarColor = mWindow.getStatusBarColor(); + mNavigationBarColor = mWindow.getNavigationBarDividerColor(); + mWindow.setStatusBarColor(Color.TRANSPARENT); + mWindow.setNavigationBarColor(Color.TRANSPARENT); + } + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + + private void restoreSystemUIColors() { + if (mWindow != null) { + if (!mDrawBarBackground) { + mWindow.clearFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + } + mWindow.setStatusBarColor(mStatusBarColor); + mWindow.setNavigationBarColor(mNavigationBarColor); + } + } + + /** + * Get the view containing the Splash Screen icon and its background. + * @see android.R.attr#windowSplashScreenAnimatedIcon + */ + public @Nullable View getIconView() { + return mIconView; + } + + /** + * Get the branding image view. + * @hide + */ + @TestApi + public @Nullable View getBrandingView() { + return mBrandingImageView; + } + + /** + * Get the initial background color of this view. + * @hide + */ + @ColorInt int getInitBackgroundColor() { + return mInitBackgroundColor; + } + + /** + * Use to create {@link SplashScreenView} object across process. + * @hide + */ + public static class SplashScreenViewParcelable implements Parcelable { + private int mIconSize; + private int mBackgroundColor; + + private Bitmap mIconBitmap; + private int mBrandingWidth; + private int mBrandingHeight; + private Bitmap mBrandingBitmap; + + private long mIconAnimationStart; + private long mIconAnimationDuration; + + public SplashScreenViewParcelable(SplashScreenView view) { + ViewGroup.LayoutParams params = view.getIconView().getLayoutParams(); + mIconSize = params.height; + mBackgroundColor = view.getInitBackgroundColor(); + + mIconBitmap = copyDrawable(view.getIconView().getBackground()); + mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground()); + params = view.getBrandingView().getLayoutParams(); + mBrandingWidth = params.width; + mBrandingHeight = params.height; + + mIconAnimationStart = view.getIconAnimationStartMillis(); + mIconAnimationDuration = view.getIconAnimationDurationMillis(); + } + + private Bitmap copyDrawable(Drawable drawable) { + if (drawable != null) { + final Rect initialBounds = drawable.copyBounds(); + final int width = initialBounds.width(); + final int height = initialBounds.height(); + + final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas bmpCanvas = new Canvas(snapshot); + drawable.setBounds(0, 0, width, height); + drawable.draw(bmpCanvas); + final Bitmap copyBitmap = snapshot.createAshmemBitmap(); + snapshot.recycle(); + return copyBitmap; + } + return null; + } + + private SplashScreenViewParcelable(@NonNull Parcel source) { + readParcel(source); + } + + private void readParcel(@NonNull Parcel source) { + mIconSize = source.readInt(); + mBackgroundColor = source.readInt(); + mIconBitmap = source.readTypedObject(Bitmap.CREATOR); + mBrandingWidth = source.readInt(); + mBrandingHeight = source.readInt(); + mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR); + mIconAnimationStart = source.readLong(); + mIconAnimationDuration = source.readLong(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mIconSize); + dest.writeInt(mBackgroundColor); + dest.writeTypedObject(mIconBitmap, flags); + dest.writeInt(mBrandingWidth); + dest.writeInt(mBrandingHeight); + dest.writeTypedObject(mBrandingBitmap, flags); + dest.writeLong(mIconAnimationStart); + dest.writeLong(mIconAnimationDuration); + } + + public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR = + new Parcelable.Creator<SplashScreenViewParcelable>() { + public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) { + return new SplashScreenViewParcelable(source); + } + public SplashScreenViewParcelable[] newArray(int size) { + return new SplashScreenViewParcelable[size]; + } + }; + + /** + * Release the bitmap if another process cannot handle it. + */ + public void clearIfNeeded() { + if (mIconBitmap != null) { + mIconBitmap.recycle(); + mIconBitmap = null; + } + if (mBrandingBitmap != null) { + mBrandingBitmap.recycle(); + mBrandingBitmap = null; + } + } + + int getIconSize() { + return mIconSize; + } + + int getBackgroundColor() { + return mBackgroundColor; + } + } +} diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 2282cc567936..63b9e9befb77 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -95,6 +95,12 @@ public final class StartingWindowInfo implements Parcelable { */ public int startingWindowTypeParameter; + /** + * Specifies a theme for the splash screen. + * @hide + */ + public int splashScreenThemeResId; + public StartingWindowInfo() { } @@ -115,6 +121,7 @@ public final class StartingWindowInfo implements Parcelable { dest.writeTypedObject(topOpaqueWindowInsetsState, flags); dest.writeTypedObject(topOpaqueWindowLayoutParams, flags); dest.writeTypedObject(mainWindowLayoutParams, flags); + dest.writeInt(splashScreenThemeResId); } void readFromParcel(@NonNull Parcel source) { @@ -124,6 +131,7 @@ public final class StartingWindowInfo implements Parcelable { topOpaqueWindowLayoutParams = source.readTypedObject( WindowManager.LayoutParams.CREATOR); mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); + splashScreenThemeResId = source.readInt(); } @Override @@ -135,7 +143,8 @@ public final class StartingWindowInfo implements Parcelable { + Integer.toHexString(startingWindowTypeParameter) + " insetsState=" + topOpaqueWindowInsetsState + " topWindowLayoutParams=" + topOpaqueWindowLayoutParams - + " mainWindowLayoutParams=" + mainWindowLayoutParams; + + " mainWindowLayoutParams=" + mainWindowLayoutParams + + " splashScreenThemeResId " + Integer.toHexString(splashScreenThemeResId); } public static final @android.annotation.NonNull Creator<StartingWindowInfo> CREATOR = diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index cdb4762a4f0a..217ade82b336 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -105,6 +105,12 @@ public class TaskOrganizer extends WindowOrganizer { public void removeStartingWindow(int taskId) {} /** + * Called when the Task want to copy the splash screen. + */ + @BinderThread + public void copySplashScreenView(int taskId) {} + + /** * Called when a task with the registered windowing mode can be controlled by this task * organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer * to show this task. @@ -223,6 +229,11 @@ public class TaskOrganizer extends WindowOrganizer { } @Override + public void copySplashScreenView(int taskId) { + mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId)); + } + + @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash)); } diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java index c2ee6461e5e1..015238788191 100644 --- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java +++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java @@ -53,10 +53,8 @@ public class HeavyWeightSwitcherActivity extends Activity { public static final String KEY_CUR_TASK = "cur_task"; /** Package of newly requested heavy-weight app. */ public static final String KEY_NEW_APP = "new_app"; - public static final String KEY_ACTIVITY_OPTIONS = "activity_options"; IntentSender mStartIntent; - Bundle mActivityOptions; boolean mHasResult; String mCurApp; int mCurTask; @@ -67,9 +65,8 @@ public class HeavyWeightSwitcherActivity extends Activity { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); - + mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT); - mActivityOptions = getIntent().getBundleExtra(KEY_ACTIVITY_OPTIONS); mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false); mCurApp = getIntent().getStringExtra(KEY_CUR_APP); mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0); @@ -151,9 +148,9 @@ public class HeavyWeightSwitcherActivity extends Activity { if (mHasResult) { startIntentSenderForResult(mStartIntent, -1, null, Intent.FLAG_ACTIVITY_FORWARD_RESULT, - Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0, mActivityOptions); + Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0); } else { - startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0, mActivityOptions); + startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0); } } catch (IntentSender.SendIntentException ex) { Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex); diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl new file mode 100644 index 000000000000..5d02a29edcd5 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.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 com.android.internal.compat; + +parcelable CompatibilityOverrideConfig; diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java new file mode 100644 index 000000000000..1c222a73eabc --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.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.internal.compat; + + +import android.app.compat.PackageOverride; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Parcelable containing compat config overrides for a given application. + * @hide + */ +public final class CompatibilityOverrideConfig implements Parcelable { + public final Map<Long, PackageOverride> overrides; + + public CompatibilityOverrideConfig(Map<Long, PackageOverride> overrides) { + this.overrides = overrides; + } + + private CompatibilityOverrideConfig(Parcel in) { + int keyCount = in.readInt(); + overrides = new HashMap<>(); + for (int i = 0; i < keyCount; i++) { + long key = in.readLong(); + PackageOverride override = in.readParcelable(PackageOverride.class.getClassLoader()); + overrides.put(key, override); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(overrides.size()); + for (Long key : overrides.keySet()) { + dest.writeLong(key); + dest.writeParcelable(overrides.get(key), 0); + } + } + + public static final Creator<CompatibilityOverrideConfig> CREATOR = + new Creator<CompatibilityOverrideConfig>() { + + @Override + public CompatibilityOverrideConfig createFromParcel(Parcel in) { + return new CompatibilityOverrideConfig(in); + } + + @Override + public CompatibilityOverrideConfig[] newArray(int size) { + return new CompatibilityOverrideConfig[size]; + } + }; +} diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl index 7aca36af919d..249c13402e4f 100644 --- a/core/java/com/android/internal/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -21,6 +21,7 @@ import com.android.internal.compat.IOverrideValidator; import java.util.Map; parcelable CompatibilityChangeConfig; +parcelable CompatibilityOverrideConfig; parcelable CompatibilityChangeInfo; /** * Platform private API for talking with the PlatformCompat service. @@ -152,6 +153,17 @@ interface IPlatformCompat { /** * Adds overrides to compatibility changes. * + * <p>Kills the app to allow the changes to take effect. + * + * @param overrides parcelable containing the compat change overrides to be applied + * @param packageName the package name of the app whose changes will be overridden + * @throws SecurityException if overriding changes is not permitted + */ + void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName); + + /** + * Adds overrides to compatibility changes. + * * <p>Does not kill the app, to be only used in tests. * * @param overrides parcelable containing the compat change overrides to be applied diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 0ede1b86b524..e602cd2c8890 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -28,6 +28,7 @@ import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; import android.graphics.Point; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.FileObserver; @@ -504,7 +505,7 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int pfdMode = ParcelFileDescriptor.parseMode(mode); if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) { - return ParcelFileDescriptor.open(file, pfdMode); + return openFileForRead(file); } else { try { // When finished writing, kick off media scanner @@ -519,6 +520,24 @@ public abstract class FileSystemProvider extends DocumentsProvider { } } + private ParcelFileDescriptor openFileForRead(final File target) throws FileNotFoundException { + final Uri uri = MediaStore.scanFile(getContext().getContentResolver(), target); + + // Passing the calling uid via EXTRA_MEDIA_CAPABILITIES_UID, so that the decision to + // transcode or not transcode can be made based upon the calling app's uid, and not based + // upon the Provider's uid. + final Bundle opts = new Bundle(); + opts.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, Binder.getCallingUid()); + + final AssetFileDescriptor afd = + getContext().getContentResolver().openTypedAssetFileDescriptor(uri, "*/*", opts); + if (afd == null) { + return null; + } + + return afd.getParcelFileDescriptor(); + } + /** * Test if the file matches the query arguments. * diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 6e9bc84156f1..cba6af98a980 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -499,7 +499,7 @@ public class InteractionJankMonitor { } public String getPerfettoTrigger() { - return String.format("interaction-jank-monitor-%d", mCujType); + return String.format("com.android.telemetry.interaction-jank-monitor-%d", mCujType); } public String getName() { diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java index af61f91841fa..4f2f973b51b1 100644 --- a/core/java/com/android/internal/os/BatterySipper.java +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -133,6 +133,12 @@ public class BatterySipper implements Comparable<BatterySipper> { public double wakeLockPowerMah; public double wifiPowerMah; public double systemServiceCpuPowerMah; + public double[] customMeasuredPowerMah; + + // Power that is re-attributed to other sippers. For example, for System Server + // this represents the power attributed to apps requesting system services. + // The value should be negative or zero. + public double powerReattributedToOtherSippersMah; // Do not include this sipper in results because it is included // in an aggregate sipper. @@ -251,6 +257,18 @@ public class BatterySipper implements Comparable<BatterySipper> { proportionalSmearMah += other.proportionalSmearMah; totalSmearedPowerMah += other.totalSmearedPowerMah; systemServiceCpuPowerMah += other.systemServiceCpuPowerMah; + if (other.customMeasuredPowerMah != null) { + if (customMeasuredPowerMah == null) { + customMeasuredPowerMah = new double[other.customMeasuredPowerMah.length]; + } + if (customMeasuredPowerMah.length == other.customMeasuredPowerMah.length) { + // This should always be true. + for (int idx = 0; idx < other.customMeasuredPowerMah.length; idx++) { + customMeasuredPowerMah[idx] += other.customMeasuredPowerMah[idx]; + } + } + } + powerReattributedToOtherSippersMah += other.powerReattributedToOtherSippersMah; } /** @@ -264,6 +282,15 @@ public class BatterySipper implements Comparable<BatterySipper> { sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah + systemServiceCpuPowerMah; + if (customMeasuredPowerMah != null) { + for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) { + totalPowerMah += customMeasuredPowerMah[idx]; + } + } + + // powerAttributedToOtherSippersMah is negative or zero + totalPowerMah = totalPowerMah + powerReattributedToOtherSippersMah; + totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah; return totalPowerMah; diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index aa5015a61f63..b20f50d62de4 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -347,6 +347,7 @@ public class BatteryStatsHelper { mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile)); mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); + mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile)); mPowerCalculators.add(new UserPowerCalculator()); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 0da2998ee9c3..87820a89280c 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -1002,8 +1002,12 @@ public class BatteryStatsImpl extends BatteryStats { int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; /** - * Accumulated energy consumption, that is not attributed to individual uids, of various - * consumers while on battery. + * Accumulated global (generally, device-wide total) energy consumption of various consumers + * while on battery. + * Its '<b>custom</b> energy buckets' correspond to the + * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer + * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). + * * If energy consumer data is completely unavailable this will be null. */ @GuardedBy("this") @@ -7157,34 +7161,34 @@ public class BatteryStatsImpl extends BatteryStats { @Override public long getScreenOnEnergy() { - if (mGlobalMeasuredEnergyStats == null) { - return ENERGY_DATA_UNAVAILABLE; - } - return mGlobalMeasuredEnergyStats - .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON); + return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON); } @Override public long getScreenDozeEnergy() { - if (mGlobalMeasuredEnergyStats == null) { - return ENERGY_DATA_UNAVAILABLE; - } - return mGlobalMeasuredEnergyStats - .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE); + return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE); } /** - * Returns the energy in microjoules that the given custom energy bucket consumed. + * Returns the energy in microjoules that the given standard energy bucket consumed. * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable * - * @param customEnergyBucket custom energy bucket of interest - * @return energy (in microjoules) used by this uid for this energy bucket + * @param bucket standard energy bucket of interest + * @return energy (in microjoules) used for this energy bucket */ - public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) { + private long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) { if (mGlobalMeasuredEnergyStats == null) { return ENERGY_DATA_UNAVAILABLE; } - return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket); + return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket); + } + + @Override + public @Nullable long[] getCustomMeasuredEnergiesMicroJoules() { + if (mGlobalMeasuredEnergyStats == null) { + return null; + } + return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergies(); } @Override public long getStartClockTime() { @@ -7533,9 +7537,16 @@ public class BatteryStatsImpl extends BatteryStats { */ private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>(); - /** Measured energies attributed to this uid while on battery. */ - // We do not use a SparseArray<LongSamplingCounters> since it would cause lots of - // unnecessary timebase references, and we're just going to use on-battery anyway... + /** + * Measured energies attributed to this uid while on battery. + * Its '<b>custom</b> energy buckets' correspond to the + * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer + * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). + * + * Will be null if energy consumer data is completely unavailable (in which case + * {@link #mGlobalMeasuredEnergyStats} will also be null) or if the power usage by this uid + * is 0 for every bucket. + */ private MeasuredEnergyStats mUidMeasuredEnergyStats; /** @@ -7978,20 +7989,16 @@ public class BatteryStatsImpl extends BatteryStats { return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket); } - /** - * Returns the energy used by this uid for a custom energy bucket of interest. - * @param customEnergyBucket custom energy bucket of interest - * @return energy (in microjoules) used by this uid for this energy bucket - */ - public long getCustomMeasuredEnergyMicroJoules(int customEnergyBucket) { - if (mBsi.mGlobalMeasuredEnergyStats == null - || !mBsi.mGlobalMeasuredEnergyStats.isValidCustomBucket(customEnergyBucket)) { - return ENERGY_DATA_UNAVAILABLE; + @Override + public long[] getCustomMeasuredEnergiesMicroJoules() { + if (mBsi.mGlobalMeasuredEnergyStats == null) { + return null; } if (mUidMeasuredEnergyStats == null) { - return 0L; // It is supported, but was never filled, so it must be 0 + // Custom buckets may exist. But all values for this uid are 0 so we report all 0s. + return new long[mBsi.mGlobalMeasuredEnergyStats.getNumberCustomEnergyBuckets()]; } - return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergy(customEnergyBucket); + return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergies(); } /** @@ -12527,11 +12534,8 @@ public class BatteryStatsImpl extends BatteryStats { final int uidInt = mapUid(uidEnergies.keyAt(i)); final long uidEnergyUJ = uidEnergies.valueAt(i); if (uidEnergyUJ == 0) continue; - // TODO: Worry about uids not in BSI currently, including uninstalled uids 'coming back' - // Specifically: What if the uid had been removed? We'll re-create it now. - // And if we instead use getAvailableUidStatsLocked() and chec for null, then we might - // not create a Uid even when we should be (say, the app's first event, somehow, was to - // use GPU). I guess that CPU/kernel data might already have this problem? + // TODO(b/180030409): Worry about dead Uids (no longer in BSI) being revived by this, + // or converse problem of not creating a new Uid if its first blame is recorded here. final Uid uidObj = getUidStatsLocked(uidInt); uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true); } @@ -13006,16 +13010,17 @@ public class BatteryStatsImpl extends BatteryStats { mWakeLockAllocationsUs = null; final long startTimeMs = mClocks.uptimeMillis(); final long elapsedRealtimeMs = mClocks.elapsedRealtime(); + final List<Integer> uidsToRemove = new ArrayList<>(); mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { - mCpuUidFreqTimeReader.removeUid(uid); + uidsToRemove.add(uid); if (DEBUG) Slog.d(TAG, "Got freq readings for an isolated uid: " + uid); return; } if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { if (DEBUG) Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid); - mCpuUidFreqTimeReader.removeUid(uid); + uidsToRemove.add(uid); return; } final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs); @@ -13074,6 +13079,9 @@ public class BatteryStatsImpl extends BatteryStats { } } }); + for (int uid : uidsToRemove) { + mCpuUidFreqTimeReader.removeUid(uid); + } final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs; if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) { @@ -13120,21 +13128,25 @@ public class BatteryStatsImpl extends BatteryStats { public void readKernelUidCpuActiveTimesLocked(boolean onBattery) { final long startTimeMs = mClocks.uptimeMillis(); final long elapsedRealtimeMs = mClocks.elapsedRealtime(); + final List<Integer> uidsToRemove = new ArrayList<>(); mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { - mCpuUidActiveTimeReader.removeUid(uid); + uidsToRemove.add(uid); if (DEBUG) Slog.w(TAG, "Got active times for an isolated uid: " + uid); return; } if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { if (DEBUG) Slog.w(TAG, "Got active times for an invalid user's uid " + uid); - mCpuUidActiveTimeReader.removeUid(uid); + uidsToRemove.add(uid); return; } final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs); u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery); }); + for (int uid : uidsToRemove) { + mCpuUidActiveTimeReader.removeUid(uid); + } final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs; if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) { @@ -13150,21 +13162,25 @@ public class BatteryStatsImpl extends BatteryStats { public void readKernelUidCpuClusterTimesLocked(boolean onBattery) { final long startTimeMs = mClocks.uptimeMillis(); final long elapsedRealtimeMs = mClocks.elapsedRealtime(); + final List<Integer> uidsToRemove = new ArrayList<>(); mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { - mCpuUidClusterTimeReader.removeUid(uid); + uidsToRemove.add(uid); if (DEBUG) Slog.w(TAG, "Got cluster times for an isolated uid: " + uid); return; } if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { if (DEBUG) Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid); - mCpuUidClusterTimeReader.removeUid(uid); + uidsToRemove.add(uid); return; } final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs); u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery); }); + for (int uid : uidsToRemove) { + mCpuUidClusterTimeReader.removeUid(uid); + } final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs; if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) { @@ -14527,8 +14543,13 @@ public class BatteryStatsImpl extends BatteryStats { */ @GuardedBy("this") public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) { - if (mGlobalMeasuredEnergyStats == null) return; - dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats); + pw.printf("On battery measured energy stats (microjoules) \n"); + if (mGlobalMeasuredEnergyStats == null) { + pw.printf(" Not supported on this device.\n"); + return; + } + + dumpMeasuredEnergyStatsLocked(pw, "global usage", mGlobalMeasuredEnergyStats); int size = mUidStats.size(); for (int i = 0; i < size; i++) { @@ -14545,7 +14566,8 @@ public class BatteryStatsImpl extends BatteryStats { MeasuredEnergyStats stats) { if (stats == null) return; final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " "); - iPw.printf("On battery measured energy stats for %s:\n", name); + iPw.increaseIndent(); + iPw.printf("%s:\n", name); iPw.increaseIndent(); stats.dump(iPw); iPw.decreaseIndent(); diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 094724c00508..233ba1912dcd 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -70,10 +70,14 @@ public class BatteryUsageStatsProvider { mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile)); mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile)); mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); - + mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile)); mPowerCalculators.add(new UserPowerCalculator()); + + // It is important that SystemServicePowerCalculator be applied last, + // because it re-attributes some of the power estimated by the other + // calculators. + mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile)); } } return mPowerCalculators; @@ -127,7 +131,8 @@ public class BatteryUsageStatsProvider { final long uptimeUs = SystemClock.uptimeMillis() * 1000; final List<PowerCalculator> powerCalculators = getPowerCalculators(); - for (PowerCalculator powerCalculator : powerCalculators) { + for (int i = 0, count = powerCalculators.size(); i < count; i++) { + PowerCalculator powerCalculator = powerCalculators.get(i); powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs, query); } diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java new file mode 100644 index 000000000000..4babe8d5fe96 --- /dev/null +++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.os; + +import android.os.BatteryStats; + +/** + * Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type + * {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). + */ +public class CustomMeasuredPowerCalculator extends PowerCalculator { + public CustomMeasuredPowerCalculator(PowerProfile powerProfile) { + } + + @Override + protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + long rawUptimeUs, int statsType) { + updateCustomMeasuredPowerMah(app, u.getCustomMeasuredEnergiesMicroJoules()); + } + + private void updateCustomMeasuredPowerMah(BatterySipper sipper, long[] measuredEnergiesUJ) { + sipper.customMeasuredPowerMah = calculateMeasuredEnergiesMah(measuredEnergiesUJ); + } + + private double[] calculateMeasuredEnergiesMah(long[] measuredEnergiesUJ) { + if (measuredEnergiesUJ == null) { + return null; + } + final double[] measuredEnergiesMah = new double[measuredEnergiesUJ.length]; + for (int i = 0; i < measuredEnergiesUJ.length; i++) { + measuredEnergiesMah[i] = uJtoMah(measuredEnergiesUJ[i]); + } + return measuredEnergiesMah; + } +} diff --git a/core/java/com/android/internal/os/KernelCpuBpfTracking.java b/core/java/com/android/internal/os/KernelCpuBpfTracking.java new file mode 100644 index 000000000000..28525478be05 --- /dev/null +++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +/** CPU tracking using eBPF. */ +public final class KernelCpuBpfTracking { + private KernelCpuBpfTracking() { + } + + /** Returns whether CPU tracking using eBPF is supported. */ + public static native boolean isSupported(); +} diff --git a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java index 50331e3338dc..06760e140de1 100644 --- a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java +++ b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java @@ -23,9 +23,6 @@ public final class KernelCpuTotalBpfMapReader { private KernelCpuTotalBpfMapReader() { } - /** Returns whether total CPU time is measured. */ - public static native boolean isSupported(); - /** Reads total CPU time from bpf map. */ public static native boolean read(Callback callback); diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java index 955f6afe579c..5c0eeb2f54f9 100644 --- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java +++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java @@ -20,12 +20,12 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Process; import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; -import java.util.Arrays; import java.util.List; /** @@ -36,87 +36,112 @@ public class SystemServicePowerCalculator extends PowerCalculator { private static final boolean DEBUG = false; private static final String TAG = "SystemServicePowerCalc"; - private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000; - - private final PowerProfile mPowerProfile; - - // Tracks system server CPU [cluster][speed] power in milliAmp-microseconds - // Data organized like this: + // Power estimators per CPU cluster, per CPU frequency. The array is flattened according + // to this layout: // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...} - private double[] mSystemServicePowerMaUs; + private final UsageBasedPowerEstimator[] mPowerEstimators; public SystemServicePowerCalculator(PowerProfile powerProfile) { - mPowerProfile = powerProfile; + int numFreqs = 0; + final int numCpuClusters = powerProfile.getNumCpuClusters(); + for (int cluster = 0; cluster < numCpuClusters; cluster++) { + numFreqs += powerProfile.getNumSpeedStepsInCpuCluster(cluster); + } + + mPowerEstimators = new UsageBasedPowerEstimator[numFreqs]; + int index = 0; + for (int cluster = 0; cluster < numCpuClusters; cluster++) { + final int numSpeeds = powerProfile.getNumSpeedStepsInCpuCluster(cluster); + for (int speed = 0; speed < numSpeeds; speed++) { + mPowerEstimators[index++] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForCpuCore(cluster, speed)); + } + } } @Override public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { - calculateSystemServicePower(batteryStats); - super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query); - } + double systemServicePowerMah = calculateSystemServicePower(batteryStats); + final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = + builder.getUidBatteryConsumerBuilders(); + final UidBatteryConsumer.Builder systemServerConsumer = uidBatteryConsumerBuilders.get( + Process.SYSTEM_UID); + + if (systemServerConsumer != null) { + systemServicePowerMah = Math.min(systemServicePowerMah, + systemServerConsumer.getTotalPower()); + + // The system server power needs to be adjusted because part of it got + // distributed to applications + systemServerConsumer.setConsumedPower( + BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS, + -systemServicePowerMah); + } - @Override - protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, - long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { - app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES, - calculateSystemServerCpuPowerMah(u)); + for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { + final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + if (app != systemServerConsumer) { + final BatteryStats.Uid uid = app.getBatteryStatsUid(); + app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES, + systemServicePowerMah * uid.getProportionalSystemServiceUsage()); + } + } } @Override public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - calculateSystemServicePower(batteryStats); - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); - } + double systemServicePowerMah = calculateSystemServicePower(batteryStats); + BatterySipper systemServerSipper = null; + for (int i = sippers.size() - 1; i >= 0; i--) { + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + if (app.getUid() == Process.SYSTEM_UID) { + systemServerSipper = app; + break; + } + } + } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - app.systemServiceCpuPowerMah = calculateSystemServerCpuPowerMah(u); + if (systemServerSipper != null) { + systemServicePowerMah = Math.min(systemServicePowerMah, systemServerSipper.sumPower()); + + // The system server power needs to be adjusted because part of it got + // distributed to applications + systemServerSipper.powerReattributedToOtherSippersMah = -systemServicePowerMah; + } + + for (int i = sippers.size() - 1; i >= 0; i--) { + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + if (app != systemServerSipper) { + final BatteryStats.Uid uid = app.uidObj; + app.systemServiceCpuPowerMah = + systemServicePowerMah * uid.getProportionalSystemServiceUsage(); + } + } + } } - private void calculateSystemServicePower(BatteryStats batteryStats) { + private double calculateSystemServicePower(BatteryStats batteryStats) { final long[] systemServiceTimeAtCpuSpeeds = batteryStats.getSystemServiceTimeAtCpuSpeeds(); if (systemServiceTimeAtCpuSpeeds == null) { - return; + return 0; } - if (mSystemServicePowerMaUs == null) { - mSystemServicePowerMaUs = new double[systemServiceTimeAtCpuSpeeds.length]; - } - int index = 0; - final int numCpuClusters = mPowerProfile.getNumCpuClusters(); - for (int cluster = 0; cluster < numCpuClusters; cluster++) { - final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); - for (int speed = 0; speed < numSpeeds; speed++) { - mSystemServicePowerMaUs[index] = - systemServiceTimeAtCpuSpeeds[index] - * mPowerProfile.getAveragePowerForCpuCore(cluster, speed); - index++; - } - } + // TODO(179210707): additionally account for CPU active and per cluster battery use - if (DEBUG) { - Log.d(TAG, "System service power per CPU cluster and frequency:" - + Arrays.toString(mSystemServicePowerMaUs)); + double powerMah = 0; + for (int i = 0; i < mPowerEstimators.length; i++) { + powerMah += mPowerEstimators[i].calculatePower(systemServiceTimeAtCpuSpeeds[i]); } - } - private double calculateSystemServerCpuPowerMah(BatteryStats.Uid u) { - double cpuPowerMaUs = 0; - final double proportionalUsage = u.getProportionalSystemServiceUsage(); - if (proportionalUsage > 0 && mSystemServicePowerMaUs != null) { - for (int i = 0; i < mSystemServicePowerMaUs.length; i++) { - cpuPowerMaUs += mSystemServicePowerMaUs[i] * proportionalUsage; - } + if (DEBUG) { + Log.d(TAG, "System service power:" + powerMah); } - return cpuPowerMaUs / MICROSEC_IN_HR; - } - @Override - public void reset() { - mSystemServicePowerMaUs = null; + return powerMah; } } diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java index 3f68597dc1cc..0f4767b859a3 100644 --- a/core/java/com/android/internal/os/WakelockPowerCalculator.java +++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java @@ -15,7 +15,12 @@ */ package com.android.internal.os; +import android.os.BatteryConsumer; import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Process; +import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; @@ -26,39 +31,93 @@ import java.util.List; public class WakelockPowerCalculator extends PowerCalculator { private static final String TAG = "WakelockPowerCalculator"; private static final boolean DEBUG = BatteryStatsHelper.DEBUG; - private final double mPowerWakelock; - private long mTotalAppWakelockTimeMs = 0; + private final UsageBasedPowerEstimator mPowerEstimator; + + private static class PowerAndDuration { + public long durationMs; + public double powerMah; + } public WakelockPowerCalculator(PowerProfile profile) { - mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_IDLE); + mPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_CPU_IDLE)); } @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); + public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { + final PowerAndDuration result = new PowerAndDuration(); + UidBatteryConsumer.Builder osBatteryConsumer = null; + double osPowerMah = 0; + long osDurationMs = 0; + long totalAppDurationMs = 0; + final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = + builder.getUidBatteryConsumerBuilders(); + for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { + final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + calculateApp(result, app.getBatteryStatsUid(), rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED); + app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK, result.durationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah); + totalAppDurationMs += result.durationMs; + + if (app.getUid() == Process.ROOT_UID) { + osBatteryConsumer = app; + osDurationMs = result.durationMs; + osPowerMah = result.powerMah; + } + } // The device has probably been awake for longer than the screen on // time and application wake lock time would account for. Assign // this remainder to the OS, if possible. + if (osBatteryConsumer != null) { + calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs, + BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs); + osBatteryConsumer.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK, + result.durationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah); + } + } + + @Override + public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { + final PowerAndDuration result = new PowerAndDuration(); BatterySipper osSipper = null; + double osPowerMah = 0; + long osDurationMs = 0; + long totalAppDurationMs = 0; for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == 0) { - osSipper = app; - break; + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + calculateApp(result, app.uidObj, rawRealtimeUs, statsType); + app.wakeLockTimeMs = result.durationMs; + app.wakeLockPowerMah = result.powerMah; + totalAppDurationMs += result.durationMs; + + if (app.getUid() == Process.ROOT_UID) { + osSipper = app; + osPowerMah = result.powerMah; + osDurationMs = result.durationMs; + } } } + // The device has probably been awake for longer than the screen on + // time and application wake lock time would account for. Assign + // this remainder to the OS, if possible. if (osSipper != null) { - calculateRemaining(osSipper, batteryStats, rawRealtimeUs, rawUptimeUs, statsType); + calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, + osPowerMah, osDurationMs, totalAppDurationMs); + osSipper.wakeLockTimeMs = result.durationMs; + osSipper.wakeLockPowerMah = result.powerMah; osSipper.sumPower(); } } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { + private void calculateApp(PowerAndDuration result, BatteryStats.Uid u, long rawRealtimeUs, + int statsType) { long wakeLockTimeUs = 0; final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats(); @@ -73,34 +132,29 @@ public class WakelockPowerCalculator extends PowerCalculator { wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType); } } - app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis - mTotalAppWakelockTimeMs += app.wakeLockTimeMs; + result.durationMs = wakeLockTimeUs / 1000; // convert to millis // Add cost of holding a wake lock. - app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000 * 60 * 60); - if (DEBUG && app.wakeLockPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs - + " power=" + formatCharge(app.wakeLockPowerMah)); + result.powerMah = mPowerEstimator.calculatePower(result.durationMs); + if (DEBUG && result.powerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": wake " + result.durationMs + + " power=" + formatCharge(result.powerMah)); } } - private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000; - wakeTimeMillis -= mTotalAppWakelockTimeMs - + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000); + private void calculateRemaining(PowerAndDuration result, BatteryStats stats, long rawRealtimeUs, + long rawUptimeUs, int statsType, double osPowerMah, long osDurationMs, + long totalAppDurationMs) { + final long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000 + - stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000 + - totalAppDurationMs; if (wakeTimeMillis > 0) { - final double power = (wakeTimeMillis * mPowerWakelock) / (1000 * 60 * 60); + final double power = mPowerEstimator.calculatePower(wakeTimeMillis); if (DEBUG) { Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + formatCharge(power)); } - app.wakeLockTimeMs += wakeTimeMillis; - app.wakeLockPowerMah += power; + result.durationMs = osDurationMs + wakeTimeMillis; + result.powerMah = osPowerMah + power; } } - - @Override - public void reset() { - mTotalAppWakelockTimeMs = 0; - } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index adebde016bb3..9840013935f8 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -110,6 +110,7 @@ import android.widget.FrameLayout; import android.widget.PopupWindow; import com.android.internal.R; +import com.android.internal.graphics.drawable.BackgroundBlurDrawable; import com.android.internal.policy.PhoneWindow.PanelFeatureState; import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback; import com.android.internal.view.FloatingActionMode; @@ -255,6 +256,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Drawable mOriginalBackgroundDrawable; private Drawable mLastOriginalBackgroundDrawable; private Drawable mResizingBackgroundDrawable; + private BackgroundBlurDrawable mBackgroundBlurDrawable; /** * Temporary holder for a window background when it is set before {@link #mWindow} is @@ -280,9 +282,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private final Paint mLegacyNavigationBarBackgroundPaint = new Paint(); private Insets mBackgroundInsets = Insets.NONE; private Insets mLastBackgroundInsets = Insets.NONE; + private int mLastBackgroundBlurRadius = 0; private boolean mDrawLegacyNavigationBarBackground; private PendingInsetsController mPendingInsetsController = new PendingInsetsController(); + private final ViewTreeObserver.OnPreDrawListener mBackgroundBlurOnPreDrawListener = () -> { + updateBackgroundBlur(); + return true; + }; DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { @@ -1263,18 +1270,27 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (mBackgroundInsets == null) { mBackgroundInsets = Insets.NONE; } + if (mBackgroundInsets.equals(mLastBackgroundInsets) + && mWindow.mBackgroundBlurRadius == mLastBackgroundBlurRadius && mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) { return; } - if (mOriginalBackgroundDrawable == null || mBackgroundInsets.equals(Insets.NONE)) { - // Call super since we are intercepting setBackground on this class. - super.setBackgroundDrawable(mOriginalBackgroundDrawable); - } else { + Drawable destDrawable = mOriginalBackgroundDrawable; + if (mWindow.mBackgroundBlurRadius > 0 && getViewRootImpl() != null + && mWindow.isTranslucent()) { + if (mBackgroundBlurDrawable == null) { + mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable(); + } + destDrawable = new LayerDrawable(new Drawable[] {mBackgroundBlurDrawable, + mOriginalBackgroundDrawable}); + mLastBackgroundBlurRadius = mWindow.mBackgroundBlurRadius; + } - // Call super since we are intercepting setBackground on this class. - super.setBackgroundDrawable(new InsetDrawable(mOriginalBackgroundDrawable, + + if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) { + destDrawable = new InsetDrawable(destDrawable, mBackgroundInsets.left, mBackgroundInsets.top, mBackgroundInsets.right, mBackgroundInsets.bottom) { @@ -1286,12 +1302,32 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public boolean getPadding(Rect padding) { return getDrawable().getPadding(padding); } - }); + }; } + + // Call super since we are intercepting setBackground on this class. + super.setBackgroundDrawable(destDrawable); + mLastBackgroundInsets = mBackgroundInsets; mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable; } + private void updateBackgroundBlur() { + if (mBackgroundBlurDrawable == null) return; + + // If the blur radius is 0, the blur region won't be sent to surface flinger, so we don't + // need to calculate the corner radius. + if (mWindow.mBackgroundBlurRadius > 0) { + if (mOriginalBackgroundDrawable != null) { + final Outline outline = new Outline(); + mOriginalBackgroundDrawable.getOutline(outline); + mBackgroundBlurDrawable.setCornerRadius(outline.mMode == Outline.MODE_ROUND_RECT + ? outline.getRadius() : 0); + } + } + mBackgroundBlurDrawable.setBlurRadius(mWindow.mBackgroundBlurRadius); + } + @Override public Drawable getBackground() { return mOriginalBackgroundDrawable; @@ -1722,6 +1758,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind cb.onAttachedToWindow(); } + getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener); + updateBackgroundDrawable(); + if (mFeatureId == -1) { /* * The main window has been attached, try to restore any panels @@ -1755,6 +1794,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind cb.onDetachedFromWindow(); } + getViewTreeObserver().removeOnPreDrawListener(mBackgroundBlurOnPreDrawListener); + if (mWindow.mDecorContentParent != null) { mWindow.mDecorContentParent.dismissPopups(); } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 5df175e8aee5..d06413c3ca15 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -258,6 +258,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { Drawable mBackgroundDrawable = null; Drawable mBackgroundFallbackDrawable = null; + int mBackgroundBlurRadius = 0; + private boolean mLoadElevation = true; private float mElevation; @@ -1523,6 +1525,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override + public final void setBackgroundBlurRadius(int blurRadius) { + super.setBackgroundBlurRadius(blurRadius); + if (getContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_CROSS_LAYER_BLUR)) { + mBackgroundBlurRadius = Math.max(blurRadius, 0); + } + } + + @Override public final void setFeatureDrawableResource(int featureId, int resId) { if (resId != 0) { DrawableFeatureState st = getDrawableState(featureId, true); @@ -2549,6 +2560,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { android.R.styleable.Window_windowBlurBehindRadius, 0); } + setBackgroundBlurRadius(a.getDimensionPixelSize( + R.styleable.Window_windowBackgroundBlurRadius, 0)); + if (params.windowAnimations == 0) { params.windowAnimations = a.getResourceId( diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index 38ef55c065a0..d7b4d78c56cf 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -238,6 +238,7 @@ public class MeasuredEnergyStats { * Return accumulated energy (in microjoules) for the a custom energy bucket since last reset. * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable. */ + @VisibleForTesting public long getAccumulatedCustomBucketEnergy(int customBucket) { if (!isValidCustomBucket(customBucket)) { return ENERGY_DATA_UNAVAILABLE; @@ -246,6 +247,17 @@ public class MeasuredEnergyStats { } /** + * Return accumulated energies (in microjoules) for all custom energy buckets since last reset. + */ + public @NonNull long[] getAccumulatedCustomBucketEnergies() { + final long[] energies = new long[getNumberCustomEnergyBuckets()]; + for (int bucket = 0; bucket < energies.length; bucket++) { + energies[bucket] = mAccumulatedEnergiesMicroJoules[customBucketToIndex(bucket)]; + } + return energies; + } + + /** * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}. */ public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) { @@ -404,7 +416,6 @@ public class MeasuredEnergyStats { /** Dump debug data. */ public void dump(PrintWriter pw) { - pw.println("Accumulated energy since last reset (microjoules):"); pw.print(" "); for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) { pw.print(getBucketName(index)); @@ -431,6 +442,11 @@ public class MeasuredEnergyStats { return "CUSTOM_" + indexToCustomBucket(index); } + /** Get the number of custom energy buckets on this device. */ + public int getNumberCustomEnergyBuckets() { + return mAccumulatedEnergiesMicroJoules.length - NUMBER_STANDARD_ENERGY_BUCKETS; + } + private static int customBucketToIndex(int customBucket) { return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS; } @@ -450,6 +466,7 @@ public class MeasuredEnergyStats { } /** Returns whether the given custom bucket is valid (exists) on this device. */ + @VisibleForTesting public boolean isValidCustomBucket(int customBucket) { return customBucket >= 0 && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length; diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index f42f468aefa0..dc6880e4f997 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -219,7 +219,7 @@ public class LatencyTracker { } private static String getTraceTriggerNameForAction(@Action int action) { - return "latency-tracker-" + getNameOfAction(STATSD_ACTION[action]); + return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]); } public static boolean isEnabled(Context ctx) { diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java index 9c87c697a737..c7585046cf9c 100644 --- a/core/java/com/android/internal/util/PerfettoTrigger.java +++ b/core/java/com/android/internal/util/PerfettoTrigger.java @@ -16,6 +16,7 @@ package com.android.internal.util; +import android.os.SystemClock; import android.util.Log; import java.io.IOException; @@ -27,16 +28,28 @@ import java.io.IOException; public class PerfettoTrigger { private static final String TAG = "PerfettoTrigger"; private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto"; + private static final long THROTTLE_MILLIS = 60000; + private static volatile long sLastTriggerTime = -THROTTLE_MILLIS; /** * @param triggerName The name of the trigger. Must match the value defined in the AOT * Perfetto config. */ public static void trigger(String triggerName) { + // Trace triggering has a non-negligible cost (fork+exec). + // To mitigate potential excessive triggering by the API client we ignore calls that happen + // too quickl after the most recent trigger. + long sinceLastTrigger = SystemClock.elapsedRealtime() - sLastTriggerTime; + if (sinceLastTrigger < THROTTLE_MILLIS) { + Log.v(TAG, "Not triggering " + triggerName + " - not enough time since last trigger"); + return; + } + try { ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName); Log.v(TAG, "Triggering " + String.join(" ", pb.command())); - Process process = pb.start(); + pb.start(); + sLastTriggerTime = SystemClock.elapsedRealtime(); } catch (IOException e) { Log.w(TAG, "Failed to trigger " + triggerName, e); } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index cb586d660634..8982519eff96 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -1236,6 +1236,8 @@ public class SystemConfig { if (IncrementalManager.isFeatureEnabled()) { addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0); + addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION, + IncrementalManager.isV2Available() ? 2 : 1); } if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 8edc8a186c59..6983d35c5a3f 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -184,6 +184,7 @@ cc_library_shared { "com_android_internal_net_NetworkUtilsInternal.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_FuseAppLoop.cpp", + "com_android_internal_os_KernelCpuBpfTracking.cpp", "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp", "com_android_internal_os_KernelCpuUidBpfMapReader.cpp", "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp", @@ -207,6 +208,7 @@ cc_library_shared { ], shared_libs: [ + "android.hardware.memtrack-unstable-ndk_platform", "audioclient-types-aidl-cpp", "audioflinger-aidl-cpp", "av-types-aidl-cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 8879111bc2f5..38bcc0f4c59e 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -190,6 +190,7 @@ extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); +extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env); extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* env); extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env); extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env); @@ -1586,6 +1587,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_security_Scrypt), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), REG_JNI(register_com_android_internal_os_FuseAppLoop), + REG_JNI(register_com_android_internal_os_KernelCpuBpfTracking), REG_JNI(register_com_android_internal_os_KernelCpuTotalBpfMapReader), REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader), REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader), diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index c64174b93c0d..8dcb2100b5fb 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -33,6 +33,7 @@ #include <string> #include <vector> +#include <aidl/android/hardware/memtrack/DeviceInfo.h> #include <android-base/logging.h> #include <bionic/malloc.h> #include <debuggerd/client.h> @@ -45,6 +46,7 @@ #include "jni.h" #include <dmabufinfo/dmabuf_sysfs_stats.h> #include <dmabufinfo/dmabufinfo.h> +#include <dmabufinfo/dmabuf_sysfs_stats.h> #include <meminfo/procmeminfo.h> #include <meminfo/sysmeminfo.h> #include <memtrack/memtrack.h> @@ -520,14 +522,15 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, } if (outUssSwapPssRss != NULL) { - if (env->GetArrayLength(outUssSwapPssRss) >= 1) { + int outLen = env->GetArrayLength(outUssSwapPssRss); + if (outLen >= 1) { jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0); if (outUssSwapPssRssArray != NULL) { outUssSwapPssRssArray[0] = uss; - if (env->GetArrayLength(outUssSwapPssRss) >= 2) { + if (outLen >= 2) { outUssSwapPssRssArray[1] = swapPss; } - if (env->GetArrayLength(outUssSwapPssRss) >= 3) { + if (outLen >= 3) { outUssSwapPssRssArray[2] = rss; } } @@ -536,10 +539,20 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, } if (outMemtrack != NULL) { - if (env->GetArrayLength(outMemtrack) >= 1) { + int outLen = env->GetArrayLength(outMemtrack); + if (outLen >= 1) { jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0); if (outMemtrackArray != NULL) { outMemtrackArray[0] = memtrack; + if (outLen >= 2) { + outMemtrackArray[1] = graphics_mem.graphics; + } + if (outLen >= 3) { + outMemtrackArray[2] = graphics_mem.gl; + } + if (outLen >= 4) { + outMemtrackArray[3] = graphics_mem.other; + } } env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0); } @@ -816,6 +829,16 @@ static jlong android_os_Debug_getDmabufTotalExportedKb(JNIEnv* env, jobject claz return dmabufTotalSizeKb; } +static jlong android_os_Debug_getDmabufHeapTotalExportedKb(JNIEnv* env, jobject clazz) { + jlong dmabufHeapTotalSizeKb = -1; + uint64_t size; + + if (meminfo::ReadDmabufHeapTotalExportedKb(&size)) { + dmabufHeapTotalSizeKb = size; + } + return dmabufHeapTotalSizeKb; +} + static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) { jlong poolsSizeKb = -1; uint64_t size; @@ -838,6 +861,31 @@ static jlong android_os_Debug_getDmabufHeapPoolsSizeKb(JNIEnv* env, jobject claz return poolsSizeKb; } +static jlong android_os_Debug_getGpuDmaBufUsageKb(JNIEnv* env, jobject clazz) { + std::vector<aidl::android::hardware::memtrack::DeviceInfo> gpu_device_info; + if (!memtrack_gpu_device_info(&gpu_device_info)) { + return -1; + } + + dmabufinfo::DmabufSysfsStats stats; + if (!GetDmabufSysfsStats(&stats)) { + return -1; + } + + jlong sizeKb = 0; + const auto& importer_stats = stats.importer_info(); + for (const auto& dev_info : gpu_device_info) { + const auto& importer_info = importer_stats.find(dev_info.name); + if (importer_info == importer_stats.end()) { + continue; + } + + sizeKb += importer_info->second.size; + } + + return sizeKb; +} + static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) { jlong dmabufPss = 0; std::vector<dmabufinfo::DmaBuffer> dmabufs; @@ -946,6 +994,10 @@ static const JNINativeMethod gMethods[] = { (void*)android_os_Debug_getIonHeapsSizeKb }, { "getDmabufTotalExportedKb", "()J", (void*)android_os_Debug_getDmabufTotalExportedKb }, + { "getGpuDmaBufUsageKb", "()J", + (void*)android_os_Debug_getGpuDmaBufUsageKb }, + { "getDmabufHeapTotalExportedKb", "()J", + (void*)android_os_Debug_getDmabufHeapTotalExportedKb }, { "getIonPoolsSizeKb", "()J", (void*)android_os_Debug_getIonPoolsSizeKb }, { "getDmabufMappedSizeKb", "()J", diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp index 44bff0188544..2384efaf1a54 100644 --- a/core/jni/android_os_incremental_IncrementalManager.cpp +++ b/core/jni/android_os_incremental_IncrementalManager.cpp @@ -30,6 +30,10 @@ static jboolean nativeIsEnabled(JNIEnv* env, jobject clazz) { return IncFs_IsEnabled(); } +static jboolean nativeIsV2Available(JNIEnv* env, jobject clazz) { + return !!(IncFs_Features() & INCFS_FEATURE_V2); +} + static jboolean nativeIsIncrementalPath(JNIEnv* env, jobject clazz, jstring javaPath) { @@ -53,12 +57,12 @@ static jbyteArray nativeUnsafeGetFileSignature(JNIEnv* env, jobject clazz, jstri return result; } -static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, - {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", - (void*)nativeIsIncrementalPath}, - {"nativeUnsafeGetFileSignature", - "(Ljava/lang/String;)[B", - (void*)nativeUnsafeGetFileSignature}}; +static const JNINativeMethod method_table[] = + {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, + {"nativeIsV2Available", "()Z", (void*)nativeIsV2Available}, + {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath}, + {"nativeUnsafeGetFileSignature", "(Ljava/lang/String;)[B", + (void*)nativeUnsafeGetFileSignature}}; int register_android_os_incremental_IncrementalManager(JNIEnv* env) { return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager", diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index c08363bbb0ba..dcfa95054ada 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -377,6 +377,10 @@ jint android_os_Process_getProcessGroup(JNIEnv* env, jobject clazz, jint pid) return (int) sp; } +jint android_os_Process_createProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid) { + return createProcessGroup(uid, pid); +} + /** Sample CPUset list format: * 0-3,4,6-8 */ @@ -1358,6 +1362,7 @@ static const JNINativeMethod methods[] = { {"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset}, {"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup}, {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup}, + {"createProcessGroup", "(II)I", (void*)android_os_Process_createProcessGroup}, {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores}, {"setSwappiness", "(IZ)Z", (void*)android_os_Process_setSwappiness}, {"setArgV0", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0}, diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp new file mode 100644 index 000000000000..e6a82f6d0cb5 --- /dev/null +++ b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#include "core_jni_helpers.h" + +#include <cputimeinstate.h> + +namespace android { + +static jboolean KernelCpuBpfTracking_isSupported(JNIEnv *, jobject) { + return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE; +} + +static const JNINativeMethod methods[] = { + {"isSupported", "()Z", (void *)KernelCpuBpfTracking_isSupported}, +}; + +int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv *env) { + return RegisterMethodsOrDie(env, "com/android/internal/os/KernelCpuBpfTracking", methods, + NELEM(methods)); +} + +} // namespace android diff --git a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp index d8446ca2881d..72492381e31a 100644 --- a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp +++ b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp @@ -20,10 +20,6 @@ namespace android { -static jboolean KernelCpuTotalBpfMapReader_isSupported(JNIEnv *, jobject) { - return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE; -} - static jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject callback) { jclass callbackClass = env->GetObjectClass(callback); jmethodID callbackMethod = env->GetMethodID(callbackClass, "accept", "(IIJ)V"); @@ -51,7 +47,6 @@ static jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject ca static const JNINativeMethod methods[] = { {"read", "(Lcom/android/internal/os/KernelCpuTotalBpfMapReader$Callback;)Z", (void *)KernelCpuTotalBpfMapReader_read}, - {"isSupported", "()Z", (void *)KernelCpuTotalBpfMapReader_isSupported}, }; int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv *env) { diff --git a/core/proto/android/server/vibratorservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index 9e42e9edfd27..aab054f4bf73 100644 --- a/core/proto/android/server/vibratorservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -15,7 +15,7 @@ */ syntax = "proto2"; -package com.android.server; +package com.android.server.vibrator; option java_multiple_files = true; @@ -51,12 +51,25 @@ message ComposedProto { // A com.android.os.VibrationEffect object. message VibrationEffectProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional OneShotProto oneshot = 3; - optional WaveformProto waveform = 1; - optional PrebakedProto prebaked = 2; + optional OneShotProto oneshot = 1; + optional WaveformProto waveform = 2; + optional PrebakedProto prebaked = 3; optional ComposedProto composed = 4; } +message SyncVibrationEffectProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated VibrationEffectProto effects = 1; + repeated int32 vibrator_ids = 2; +} + +// A com.android.os.CombinedVibrationEffect object. +message CombinedVibrationEffectProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated SyncVibrationEffectProto effects = 1; + repeated int32 delays = 2; +} + message VibrationAttributesProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 usage = 1; @@ -68,30 +81,31 @@ message VibrationAttributesProto { message VibrationProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int64 start_time = 1; - optional int64 end_time = 4; - optional VibrationEffectProto effect = 2; - optional VibrationEffectProto original_effect = 3; + optional int64 end_time = 2; + optional CombinedVibrationEffectProto effect = 3; + optional CombinedVibrationEffectProto original_effect = 4; optional VibrationAttributesProto attributes = 5; optional int32 status = 6; } -// Next id: 17 -message VibratorServiceDumpProto { +// Next id: 18 +message VibratorManagerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional VibrationProto current_vibration = 1; - optional bool is_vibrating = 2; - optional VibrationProto current_external_vibration = 3; - optional bool vibrator_under_external_control = 4; - optional bool low_power_mode = 5; - optional int32 haptic_feedback_intensity = 6; - optional int32 haptic_feedback_default_intensity = 14; - optional int32 notification_intensity = 7; - optional int32 notification_default_intensity = 15; - optional int32 ring_intensity = 8; - optional int32 ring_default_intensity = 16; - repeated VibrationProto previous_ring_vibrations = 9; - repeated VibrationProto previous_notification_vibrations = 10; - repeated VibrationProto previous_alarm_vibrations = 11; - repeated VibrationProto previous_vibrations = 12; - repeated VibrationProto previous_external_vibrations = 13; + repeated int32 vibrator_ids = 1; + optional VibrationProto current_vibration = 2; + optional bool is_vibrating = 3; + optional VibrationProto current_external_vibration = 4; + optional bool vibrator_under_external_control = 5; + optional bool low_power_mode = 6; + optional int32 haptic_feedback_intensity = 7; + optional int32 haptic_feedback_default_intensity = 8; + optional int32 notification_intensity = 9; + optional int32 notification_default_intensity = 10; + optional int32 ring_intensity = 11; + optional int32 ring_default_intensity = 12; + repeated VibrationProto previous_ring_vibrations = 13; + repeated VibrationProto previous_notification_vibrations = 14; + repeated VibrationProto previous_alarm_vibrations = 15; + repeated VibrationProto previous_vibrations = 16; + repeated VibrationProto previous_external_vibrations = 17; }
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 99014c5442b0..ba43ee79e6f3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1870,6 +1870,12 @@ <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows an application to manage an automotive device's application network + preference as it relates to OEM_PAID and OEM_PRIVATE capable networks. + <p>Not for use by third-party or privileged applications. --> + <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" + android:protectionLevel="signature" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> @@ -5465,6 +5471,13 @@ <permission android:name="android.permission.MANAGE_GAME_MODE" android:protectionLevel="signature" /> + <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager + when they are performing reboot-blocking work. + @hide --> + <permission android:name="android.permission.SIGNAL_REBOOT_READINESS" + android:protectionLevel="signature|privileged" /> + + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml new file mode 100644 index 000000000000..513da5e431e5 --- /dev/null +++ b/core/res/res/layout/splash_screen_view.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<android.window.SplashScreenView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical"> + + <View android:id="@+id/splashscreen_icon_view" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center"/> + + <View android:id="@+id/splashscreen_branding_view" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginBottom="60dp"/> + +</android.window.SplashScreenView>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index aeb4fc468fe4..927a8b49c17e 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -362,6 +362,12 @@ surface when the app has not drawn any content into this area. One example is when the user is resizing a window of an activity in multi-window mode. --> <attr name="windowBackgroundFallback" format="reference|color" /> + <!-- Blur the screen behind the window with the bounds of the window. + The radius defines the size of the neighbouring area, from which pixels will be + averaged to form the final color for each pixel in the region. + A radius of 0 means no blur. The higher the radius, the denser the blur. + Corresponds to {@link android.view.Window#setBackgroundBlurRadius}. --> + <attr name="windowBackgroundBlurRadius" format="dimension" /> <!-- Drawable to use as a frame around the window. --> <attr name="windowFrame" format="reference" /> <!-- Flag indicating whether there should be no title on this window. --> @@ -1966,6 +1972,7 @@ <declare-styleable name="Window"> <attr name="windowBackground" /> <attr name="windowBackgroundFallback" /> + <attr name="windowBackgroundBlurRadius" /> <attr name="windowContentOverlay" /> <attr name="windowFrame" /> <attr name="windowNoTitle" /> @@ -2255,6 +2262,23 @@ --> <enum name="always" value="3" /> </attr> + + <!-- The background color for the splash screen, if not specify then system will + calculate from windowBackground. --> + <attr name="windowSplashScreenBackground" format="color"/> + + <!-- Replace an icon in the center of the starting window, if the object is animated + and drawable(e.g. AnimationDrawable, AnimatedVectorDrawable), then it will also + play the animation while showing the starting window. --> + <attr name="windowSplashScreenAnimatedIcon" format="reference"/> + <!-- The duration, in milliseconds, of the window splash screen icon animation duration + when playing the splash screen starting window. The maximum animation duration should + be limited below 1000ms. --> + <attr name="windowSplashScreenAnimationDuration" format="integer"/> + + <!-- Place an drawable image in the bottom of the starting window, it can be used to + represent the branding of the application. --> + <attr name="windowSplashScreenBrandingImage" format="reference"/> </declare-styleable> <!-- The set of attributes that describe a AlertDialog's theme. --> @@ -9253,6 +9277,7 @@ <attr name="shortcutShortLabel" format="reference" /> <attr name="shortcutLongLabel" format="reference" /> <attr name="shortcutDisabledMessage" format="reference" /> + <attr name="splashScreenTheme" format="reference"/> </declare-styleable> <declare-styleable name="ShortcutCategories"> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 09ca12aa9744..b7c755ef7f6b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1590,8 +1590,8 @@ take precedence over lower ones. See com.android.server.timedetector.TimeDetectorStrategy for available sources. --> <string-array name="config_autoTimeSourcesPriority"> - <item>telephony</item> <item>network</item> + <item>telephony</item> </string-array> <!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index debdab0b37ba..706641985e20 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -926,4 +926,12 @@ <dimen name="controls_thumbnail_image_max_height">140dp</dimen> <!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.--> <dimen name="controls_thumbnail_image_max_width">280dp</dimen> + + <!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. --> + <dimen name="system_app_widget_background_radius">16dp</dimen> + <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. --> + <dimen name="system_app_widget_inner_radius">8dp</dimen> + <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. --> + <dimen name="system_app_widget_internal_padding">16dp</dimen> + </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 068987ec8afe..ca40564cbe5b 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3065,6 +3065,12 @@ <public name="clipToOutline" /> <public name="edgeEffectType" /> <public name="knownCerts" /> + <public name="windowBackgroundBlurRadius"/> + <public name="windowSplashScreenBackground"/> + <public name="windowSplashScreenAnimatedIcon"/> + <public name="windowSplashScreenAnimationDuration"/> + <public name="windowSplashScreenBrandingImage"/> + <public name="splashScreenTheme" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> @@ -3105,6 +3111,11 @@ <public-group type="dimen" first-id="0x01050008"> <!-- dimension definitions go here --> + + <!-- System-provided dimensions for app widgets. --> + <public name="system_app_widget_background_radius" /> + <public name="system_app_widget_inner_radius" /> + <public name="system_app_widget_internal_padding" /> </public-group> <public-group type="bool" first-id="0x01110007"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4109d4c9f6f9..f66d33b02b69 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1818,6 +1818,9 @@ <java-symbol type="string" name="forward_intent_to_owner" /> <java-symbol type="string" name="forward_intent_to_work" /> <java-symbol type="dimen" name="cross_profile_apps_thumbnail_size" /> + <java-symbol type="layout" name="splash_screen_view" /> + <java-symbol type="id" name="splashscreen_icon_view" /> + <java-symbol type="id" name="splashscreen_branding_view" /> <!-- From services --> <java-symbol type="anim" name="screen_rotate_0_enter" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 049ba23a1a97..87ae1623ca92 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -167,6 +167,9 @@ please see themes_device_defaults.xml. <!-- Window attributes --> <item name="windowBackground">@drawable/screen_background_selector_dark</item> <item name="windowBackgroundFallback">?attr/colorBackground</item> + <item name="windowSplashScreenBackground">@color/transparent</item> + <item name="windowSplashScreenAnimatedIcon">@null</item> + <item name="windowSplashScreenBrandingImage">@null</item> <item name="windowClipToOutline">false</item> <item name="windowFrame">@null</item> <item name="windowNoTitle">false</item> diff --git a/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java new file mode 100644 index 000000000000..606e81d64289 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java @@ -0,0 +1,61 @@ +/* + * 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.content.pm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.os.Parcel; +import android.util.ArraySet; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public final class PermissionInfoTest { + private static final String KNOWN_CERT_DIGEST_1 = + "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599"; + private static final String KNOWN_CERT_DIGEST_2 = + "9369370ffcfdc1e92dae777252c05c483b8cbb55fa9d5fd9f6317f623ae6d8c6"; + + @Test + public void createFromParcel_returnsKnownCerts() { + // The platform supports a knownSigner permission protection flag that allows one or more + // trusted signing certificates to be specified with the permission declaration; if a + // requesting app is signed by any of these trusted certificates the permission is granted. + // This test verifies the Set of knownCerts is properly parceled / unparceled. + PermissionInfo permissionInfo = new PermissionInfo(); + permissionInfo.protectionLevel = + PermissionInfo.PROTECTION_SIGNATURE | PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER; + permissionInfo.knownCerts = new ArraySet<>(2); + permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_1); + permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_2); + Parcel parcel = Parcel.obtain(); + permissionInfo.writeToParcel(parcel, 0); + + parcel.setDataPosition(0); + PermissionInfo unparceledPermissionInfo = PermissionInfo.CREATOR.createFromParcel(parcel); + + assertNotNull(unparceledPermissionInfo.knownCerts); + assertEquals(2, unparceledPermissionInfo.knownCerts.size()); + assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_1)); + assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_2)); + } +} diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS index 1a28b73de8cd..9a9b4748d4f1 100644 --- a/core/tests/coretests/src/android/os/OWNERS +++ b/core/tests/coretests/src/android/os/OWNERS @@ -2,8 +2,11 @@ per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com # Haptics +per-file CombinedVibrationEffectTest.java = michaelwr@google.com per-file ExternalVibrationTest.java = michaelwr@google.com per-file VibrationEffectTest.java = michaelwr@google.com +per-file VibratorInfoTest.java = michaelwr@google.com +per-file VibratorTest.java = michaelwr@google.com # Power per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com diff --git a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java index a43b238405d1..a121941e7b73 100644 --- a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java +++ b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java @@ -16,14 +16,14 @@ package android.service.notification; -import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT; import static com.google.common.truth.Truth.assertThat; -import android.app.NotificationChannel; +import android.content.pm.VersionedPackage; import android.os.Parcel; import android.util.ArraySet; @@ -45,16 +45,19 @@ public class NotificationListenerFilterTest { assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isTrue(); assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING - | FLAG_FILTER_TYPE_SILENT); + | FLAG_FILTER_TYPE_SILENT + | FLAG_FILTER_TYPE_ONGOING); assertThat(nlf.getDisallowedPackages()).isEmpty(); - assertThat(nlf.isPackageAllowed("pkg1")).isTrue(); + assertThat(nlf.isPackageAllowed(new VersionedPackage("any", 0))).isTrue(); } @Test public void testConstructor() { - ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"}); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); + VersionedPackage a2= new VersionedPackage("pkg2", 2142534); + ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2}); NotificationListenerFilter nlf = new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs); assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse(); @@ -62,20 +65,21 @@ public class NotificationListenerFilterTest { assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse(); assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING); - assertThat(nlf.getDisallowedPackages()).contains("pkg1"); - assertThat(nlf.getDisallowedPackages()).contains("pkg2"); - assertThat(nlf.isPackageAllowed("pkg1")).isFalse(); - assertThat(nlf.isPackageAllowed("pkg2")).isFalse(); + assertThat(nlf.getDisallowedPackages()).contains(a1); + assertThat(nlf.getDisallowedPackages()).contains(a2); + assertThat(nlf.isPackageAllowed(a1)).isFalse(); + assertThat(nlf.isPackageAllowed(a2)).isFalse(); } @Test public void testSetDisallowedPackages() { NotificationListenerFilter nlf = new NotificationListenerFilter(); - ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1"}); + ArraySet<VersionedPackage> pkgs = new ArraySet<>( + new VersionedPackage[] {new VersionedPackage("pkg1", 0)}); nlf.setDisallowedPackages(pkgs); - assertThat(nlf.isPackageAllowed("pkg1")).isFalse(); + assertThat(nlf.isPackageAllowed(new VersionedPackage("pkg1", 0))).isFalse(); } @Test @@ -94,7 +98,9 @@ public class NotificationListenerFilterTest { @Test public void testDescribeContents() { final int expected = 0; - ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"}); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); + VersionedPackage a2= new VersionedPackage("pkg2", 2142534); + ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2}); NotificationListenerFilter nlf = new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs); assertThat(nlf.describeContents()).isEqualTo(expected); @@ -102,7 +108,9 @@ public class NotificationListenerFilterTest { @Test public void testParceling() { - ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"}); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); + VersionedPackage a2= new VersionedPackage("pkg2", 2142534); + ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2}); NotificationListenerFilter nlf = new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs); @@ -116,9 +124,9 @@ public class NotificationListenerFilterTest { assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse(); assertThat(nlf1.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING); - assertThat(nlf1.getDisallowedPackages()).contains("pkg1"); - assertThat(nlf1.getDisallowedPackages()).contains("pkg2"); - assertThat(nlf1.isPackageAllowed("pkg1")).isFalse(); - assertThat(nlf1.isPackageAllowed("pkg2")).isFalse(); + assertThat(nlf1.getDisallowedPackages()).contains(a1); + assertThat(nlf1.getDisallowedPackages()).contains(a2); + assertThat(nlf1.isPackageAllowed(a1)).isFalse(); + assertThat(nlf1.isPackageAllowed(a2)).isFalse(); } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index 4c52848cc079..6652c64c4344 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -707,22 +707,26 @@ public class BatteryStatsNoteTest extends TestCase { int uid2, long blame2A, long blame2B, MockBatteryStatsImpl bi) { + final long[] actualTotal = bi.getCustomMeasuredEnergiesMicroJoules(); + final long[] actualUid1 = bi.getUidStatsLocked(uid1).getCustomMeasuredEnergiesMicroJoules(); + final long[] actualUid2 = bi.getUidStatsLocked(uid2).getCustomMeasuredEnergiesMicroJoules(); + + assertNotNull(actualTotal); + assertNotNull(actualUid1); + assertNotNull(actualUid2); + assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA, - bi.getCustomMeasuredEnergyMicroJoules(0)); + actualTotal[0]); assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB, - bi.getCustomMeasuredEnergyMicroJoules(1)); + actualTotal[1]); - assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, - bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(0)); + assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, actualUid1[0]); - assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, - bi.getUidStatsLocked(uid1).getCustomMeasuredEnergyMicroJoules(1)); + assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, actualUid1[1]); - assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, - bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(0)); + assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, actualUid2[0]); - assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, - bi.getUidStatsLocked(uid2).getCustomMeasuredEnergyMicroJoules(1)); + assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]); } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index 0fac4f79686a..2e6e0de8d0c2 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -68,6 +68,7 @@ import org.junit.runners.Suite; SystemServicePowerCalculatorTest.class, UserPowerCalculatorTest.class, VideoPowerCalculatorTest.class, + WakelockPowerCalculatorTest.class, com.android.internal.power.MeasuredEnergyStatsTest.class }) diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java index 0ddc4c0035ce..5edd58fb3eb9 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java @@ -49,6 +49,7 @@ public class BatteryUsageStatsRule implements TestRule { }; private BatteryUsageStats mBatteryUsageStats; + private boolean mScreenOn; public BatteryUsageStatsRule() { Context context = InstrumentationRegistry.getContext(); @@ -97,6 +98,11 @@ public class BatteryUsageStatsRule implements TestRule { return this; } + public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) { + mScreenOn = screenOn; + return this; + } + public void setNetworkStats(NetworkStats networkStats) { mBatteryStats.setNetworkStats(networkStats); } @@ -115,6 +121,7 @@ public class BatteryUsageStatsRule implements TestRule { private void noteOnBattery() { mBatteryStats.setOnBatteryInternal(true); mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0); + mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0); } public PowerProfile getPowerProfile() { diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java index 24741fe110ce..dfbf28b286c6 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java @@ -18,9 +18,16 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.BatteryUsageStatsQuery; import android.os.Binder; import android.os.Process; +import android.os.UidBatteryConsumer; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -39,34 +46,48 @@ import java.util.Collection; @RunWith(AndroidJUnit4.class) public class SystemServicePowerCalculatorTest { - private static final double PRECISION = 0.0000001; + private static final double PRECISION = 0.000001; @Rule - public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); - + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) + .setNumCpuClusters(2) + .setNumSpeedStepsInCpuCluster(0, 2) + .setNumSpeedStepsInCpuCluster(1, 2) + .setAveragePowerForCpuCluster(0, 360) + .setAveragePowerForCpuCluster(1, 480) + .setAveragePowerForCpuCore(0, 0, 300) + .setAveragePowerForCpuCore(0, 1, 400) + .setAveragePowerForCpuCore(1, 0, 500) + .setAveragePowerForCpuCore(1, 1, 600); + + private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider; private MockBatteryStatsImpl mMockBatteryStats; private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader; private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader; @Before public void setUp() throws IOException { + mMockUserInfoProvider = mock(BatteryStatsImpl.UserInfoProvider.class); mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader(); mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader(); mMockBatteryStats = mStatsRule.getBatteryStats() .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader) .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader) - .setUserInfoProvider(new MockUserInfoProvider()); + .setUserInfoProvider(mMockUserInfoProvider); } @Test - public void testCalculateApp() { - // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total + public void testPowerProfileBasedModel() { + when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); + + // Test Power Profile has two CPU clusters with 2 speeds each, thus 4 freq times total mMockSystemServerCpuThreadReader.setCpuTimes( - new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000}, - new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000}); + new long[] {30000, 40000, 50000, 60000}, + new long[] {20000, 30000, 40000, 50000}); mMockCpuUidFreqTimeReader.setSystemServerCpuTimes( - new long[] {10000, 20000, 30000, 40000, 50000, 60000, 70000} + new long[] {10000, 20000, 30000, 40000} ); mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -101,14 +122,17 @@ public class SystemServicePowerCalculatorTest { SystemServicePowerCalculator calculator = new SystemServicePowerCalculator( mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new FakeCpuPowerCalculator(), calculator); assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid1) .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES)) - .isWithin(PRECISION).of(0.00016269); + .isWithin(PRECISION).of(1.888888); assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid2) .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES)) - .isWithin(PRECISION).of(0.00146426); + .isWithin(PRECISION).of(17.0); + assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID) + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS)) + .isWithin(PRECISION).of(-18.888888); } private static class MockKernelCpuUidFreqTimeReader extends @@ -137,7 +161,7 @@ public class SystemServicePowerCalculatorTest { } private static class MockSystemServerCpuThreadReader extends SystemServerCpuThreadReader { - private SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes(); + private final SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes(); MockSystemServerCpuThreadReader() { super(null); @@ -154,16 +178,15 @@ public class SystemServicePowerCalculatorTest { } } - private static class MockUserInfoProvider extends BatteryStatsImpl.UserInfoProvider { - @Nullable - @Override - protected int[] getUserIds() { - return new int[0]; - } - + private static class FakeCpuPowerCalculator extends PowerCalculator { @Override - public boolean exists(int userId) { - return true; + protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { + if (u.getUid() == Process.SYSTEM_UID) { + // SystemServer must be attributed at least as much power as the total + // of all system services requested by apps. + app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 1000000); + } } } } diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java new file mode 100644 index 000000000000..4f71b438c6fa --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java @@ -0,0 +1,76 @@ +/* + * 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.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Process; +import android.os.UidBatteryConsumer; +import android.os.WorkSource; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class WakelockPowerCalculatorTest { + private static final double PRECISION = 0.00001; + + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + private static final int APP_PID = 3145; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_CPU_IDLE, 360.0); + + @Test + public void testTimerBasedModel() { + mStatsRule.getUidStats(Process.ROOT_UID); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteStartWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "", + BatteryStats.WAKE_TYPE_PARTIAL, true, 1000, 1000); + batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "", + BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000); + + mStatsRule.setTime(10_000_000, 6_000_000); + + WakelockPowerCalculator calculator = + new WakelockPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK)) + .isEqualTo(1000); + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK)) + .isWithin(PRECISION).of(0.1); + + UidBatteryConsumer osConsumer = mStatsRule.getUidBatteryConsumer(Process.ROOT_UID); + assertThat(osConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK)) + .isEqualTo(5000); + assertThat(osConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK)) + .isWithin(PRECISION).of(0.5); + } +} diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java index 1679774adb35..5fd5a7838c3a 100644 --- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java @@ -405,6 +405,41 @@ public class MeasuredEnergyStatsTest { } @Test + public void testGetAccumulatedCustomBucketEnergies() { + final MeasuredEnergyStats stats + = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3); + + stats.updateCustomBucket(0, 50, true); + stats.updateCustomBucket(1, 60, true); + stats.updateCustomBucket(2, 13, true); + stats.updateCustomBucket(1, 70, true); + + final long[] output = stats.getAccumulatedCustomBucketEnergies(); + assertEquals(3, output.length); + + assertEquals(50, output[0]); + assertEquals(60 + 70, output[1]); + assertEquals(13, output[2]); + } + + @Test + public void testGetAccumulatedCustomBucketEnergies_empty() { + final MeasuredEnergyStats stats + = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0); + + final long[] output = stats.getAccumulatedCustomBucketEnergies(); + assertEquals(0, output.length); + } + + @Test + public void testGetNumberCustomEnergyBuckets() { + assertEquals(0, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0) + .getNumberCustomEnergyBuckets()); + assertEquals(3, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3) + .getNumberCustomEnergyBuckets()); + } + + @Test public void testReset() { final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS]; final int numCustomBuckets = 2; diff --git a/core/tests/coretests/src/com/android/internal/widget/OWNERS b/core/tests/coretests/src/com/android/internal/widget/OWNERS new file mode 100644 index 000000000000..b40fe240d80c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/widget/OWNERS @@ -0,0 +1,3 @@ +# LockSettings related +per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS +per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS diff --git a/data/etc/OWNERS b/data/etc/OWNERS index 65d3a012b129..549e074d297c 100644 --- a/data/etc/OWNERS +++ b/data/etc/OWNERS @@ -6,7 +6,6 @@ jeffv@google.com jsharkey@android.com jsharkey@google.com lorenzo@google.com -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com toddke@android.com diff --git a/data/etc/car/com.android.car.provision.xml b/data/etc/car/com.android.car.provision.xml index 4fd9cae53bd7..42cfd3ce2558 100644 --- a/data/etc/car/com.android.car.provision.xml +++ b/data/etc/car/com.android.car.provision.xml @@ -17,6 +17,7 @@ <permissions> <privapp-permissions package="com.android.car.provision"> <permission name="android.car.permission.CAR_POWERTRAIN"/> + <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/> <permission name="android.permission.MANAGE_USERS"/> diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml index 734561ceeb01..fa92b6da9460 100644 --- a/data/etc/com.android.emergency.xml +++ b/data/etc/com.android.emergency.xml @@ -19,6 +19,7 @@ <!-- Required to place emergency calls from emergency info screen. --> <permission name="android.permission.CALL_PRIVILEGED"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> </privapp-permissions> </permissions> diff --git a/data/etc/com.android.provision.xml b/data/etc/com.android.provision.xml index d2ea0ec085d3..68f82985102c 100644 --- a/data/etc/com.android.provision.xml +++ b/data/etc/com.android.provision.xml @@ -17,7 +17,7 @@ <permissions> <privapp-permissions package="com.android.provision"> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> - <permissionn ame="android.permission.DISPATCH_PROVISIONING_MESSAGE"/> + <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/> <permission name="android.permission.MASTER_CLEAR"/> </privapp-permissions> </permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 0484a9ab582e..403dc65abbf5 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -474,6 +474,8 @@ applications that come with the platform <!-- Permission required for GTS test - GtsAssistIntentTestCases --> <permission name="android.permission.MANAGE_SOUND_TRIGGER" /> <permission name="android.permission.CAPTURE_AUDIO_HOTWORD" /> + <!-- Permission required for CTS test - CtsRebootReadinessTestCases --> + <permission name="android.permission.SIGNAL_REBOOT_READINESS" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 222c9bdf2cb4..ded4a276880f 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -811,12 +811,6 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, - "-1144293044": { - "message": "SURFACE SET FREEZE LAYER: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowStateAnimator.java" - }, "-1142279614": { "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s", "level": "VERBOSE", @@ -895,12 +889,6 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowSurfaceController.java" }, - "-1088782910": { - "message": "Translucent=%s Floating=%s ShowWallpaper=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-1076978367": { "message": "thawRotation: mRotation=%d", "level": "VERBOSE", @@ -1261,12 +1249,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java" }, - "-639305784": { - "message": "Could not report config changes to the window token client.", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowToken.java" - }, "-639217716": { "message": "setFocusedApp %s displayId=%d Callers=%s", "level": "INFO", @@ -1417,12 +1399,6 @@ "group": "WM_DEBUG_KEEP_SCREEN_ON", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, - "-477481651": { - "message": "SURFACE DESTROY PENDING: %s. %s", - "level": "INFO", - "group": "WM_SHOW_SURFACE_ALLOC", - "at": "com\/android\/server\/wm\/WindowStateAnimator.java" - }, "-463348344": { "message": "Removing and adding activity %s to root task at top callers=%s", "level": "INFO", @@ -1693,6 +1669,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, + "-124316973": { + "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_STARTING_WINDOW", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-118786523": { "message": "Resume failed; resetting state to %s: %s", "level": "VERBOSE", @@ -1903,12 +1885,6 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "123161180": { - "message": "SEVER CHILDREN", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "140319294": { "message": "IME target changed within ActivityRecord", "level": "DEBUG", @@ -2569,12 +2545,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "838570988": { - "message": "Could not report token removal to the window token client.", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowToken.java" - }, "872933199": { "message": "Changing focus from %s to %s displayId=%d Callers=%s", "level": "DEBUG", @@ -3235,6 +3205,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "1699269281": { + "message": "Don't organize or trigger events for untrusted displayId=%d", + "level": "WARN", + "group": "WM_DEBUG_WINDOW_ORGANIZER", + "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" + }, "1720229827": { "message": "Creating animation bounds layer", "level": "INFO", diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 40c75a4d2f2f..32c777cf498c 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1346,6 +1346,19 @@ public class Typeface { } } + static { + // Preload Roboto-Regular.ttf in Zygote for improving app launch performance. + // TODO: add new attribute to fonts.xml to preload fonts in Zygote. + preloadFontFile("/system/fonts/Roboto-Regular.ttf"); + } + + private static void preloadFontFile(String filePath) { + File file = new File(filePath); + if (file.exists()) { + nativeWarmUpCache(filePath); + } + } + /** @hide */ @VisibleForTesting public static void destroySystemFontMap() { @@ -1464,4 +1477,6 @@ public class Typeface { private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer); private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface); + + private static native void nativeWarmUpCache(String fileName); } diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index b153c995a7f4..f826b24b2df2 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -27,7 +27,6 @@ import android.graphics.RectF; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.text.TextUtils; -import android.util.LongSparseLongArray; import android.util.TypedValue; import com.android.internal.annotations.GuardedBy; @@ -63,10 +62,9 @@ public final class Font { NativeAllocationRegistry.createMalloced( ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont()); - private static final Object SOURCE_ID_LOCK = new Object(); - @GuardedBy("SOURCE_ID_LOCK") - private static final LongSparseLongArray FONT_SOURCE_ID_MAP = - new LongSparseLongArray(300); // System font has 200 fonts, so 300 should be enough. + private static final NativeAllocationRegistry FONT_REGISTRY = + NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(), + nGetReleaseNativeFont()); /** * A builder class for creating new Font. @@ -519,18 +517,19 @@ public final class Font { private @Nullable FontVariationAxis[] mAxes = null; @GuardedBy("mLock") private @NonNull LocaleList mLocaleList = null; - @GuardedBy("mLock") - private int mSourceIdentifier = -1; /** * Use Builder instead * * Caller must increment underlying minikin::Font ref count. + * This class takes the ownership of the passing native objects. * * @hide */ public Font(long nativePtr) { mNativePtr = nativePtr; + + FONT_REGISTRY.registerNativeAllocation(this, mNativePtr); } /** @@ -751,20 +750,7 @@ public final class Font { * @return an unique identifier for the font source data. */ public int getSourceIdentifier() { - synchronized (mLock) { - if (mSourceIdentifier == -1) { - long bufferAddress = nGetBufferAddress(mNativePtr); - synchronized (SOURCE_ID_LOCK) { - long id = FONT_SOURCE_ID_MAP.get(bufferAddress, -1); - if (id == -1) { - id = FONT_SOURCE_ID_MAP.size(); - FONT_SOURCE_ID_MAP.append(bufferAddress, id); - } - mSourceIdentifier = (int) id; - } - } - return mSourceIdentifier; - } + return nGetSourceId(mNativePtr); } /** @@ -883,6 +869,9 @@ public final class Font { private static native long nGetBufferAddress(long font); @CriticalNative + private static native int nGetSourceId(long font); + + @CriticalNative private static native long nGetReleaseNativeFont(); @FastNative diff --git a/graphics/java/android/graphics/pdf/OWNERS b/graphics/java/android/graphics/pdf/OWNERS index f04e2008a437..057dc0d9583c 100644 --- a/graphics/java/android/graphics/pdf/OWNERS +++ b/graphics/java/android/graphics/pdf/OWNERS @@ -5,4 +5,3 @@ djsollen@google.com sumir@google.com svetoslavganov@android.com svetoslavganov@google.com -moltmann@google.com diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 334b1110d651..988838b46334 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -17,6 +17,7 @@ package android.security.keystore; import android.annotation.Nullable; +import android.content.Context; import android.os.Build; import android.security.Credentials; import android.security.KeyPairGeneratorSpec; @@ -25,6 +26,8 @@ import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterCertificateChain; import android.security.keymaster.KeymasterDefs; +import android.telephony.TelephonyManager; +import android.util.ArraySet; import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector; import com.android.internal.org.bouncycastle.asn1.ASN1InputStream; @@ -477,11 +480,11 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato success = true; return keyPair; - } catch (ProviderException e) { + } catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) { if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { throw new SecureKeyImportUnavailableException(e); } else { - throw e; + throw new ProviderException(e); } } finally { if (!success) { @@ -491,7 +494,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair) - throws ProviderException { + throws ProviderException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); if (challenge != null) { KeymasterArguments args = new KeymasterArguments(); @@ -510,6 +513,60 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato Build.MODEL.getBytes(StandardCharsets.UTF_8)); } + int[] idTypes = mSpec.getAttestationIds(); + if (idTypes != null) { + final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); + for (int idType : idTypes) { + idTypesSet.add(idType); + } + TelephonyManager telephonyService = null; + if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI) + || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) { + telephonyService = + (TelephonyManager) KeyStore.getApplicationContext().getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyService == null) { + throw new DeviceIdAttestationException( + "Unable to access telephony service"); + } + } + for (final Integer idType : idTypesSet) { + switch (idType) { + case AttestationUtils.ID_TYPE_SERIAL: + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, + Build.getSerial().getBytes(StandardCharsets.UTF_8) + ); + break; + case AttestationUtils.ID_TYPE_IMEI: { + final String imei = telephonyService.getImei(0); + if (imei == null) { + throw new DeviceIdAttestationException("Unable to retrieve IMEI"); + } + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, + imei.getBytes(StandardCharsets.UTF_8) + ); + break; + } + case AttestationUtils.ID_TYPE_MEID: { + final String meid = telephonyService.getMeid(0); + if (meid == null) { + throw new DeviceIdAttestationException("Unable to retrieve MEID"); + } + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, + meid.getBytes(StandardCharsets.UTF_8) + ); + break; + } + case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: { + args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION); + break; + } + default: + throw new IllegalArgumentException("Unknown device ID type " + idType); + } + } + } + return getAttestationChain(privateKeyAlias, keyPair, args); } @@ -547,7 +604,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } - private KeymasterArguments constructKeyGenerationArguments() { + private KeymasterArguments constructKeyGenerationArguments() + throws IllegalArgumentException, DeviceIdAttestationException { KeymasterArguments args = new KeymasterArguments(); args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); @@ -565,9 +623,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mSpec.getKeyValidityForConsumptionEnd()); addAlgorithmSpecificParameters(args); - if (mSpec.isUniqueIdIncluded()) + if (mSpec.isUniqueIdIncluded()) { args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID); - + } return args; } diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java index c8c1de4a5e83..f22b6041800f 100644 --- a/keystore/java/android/security/keystore/ArrayUtils.java +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -34,6 +34,14 @@ public abstract class ArrayUtils { return ((array != null) && (array.length > 0)) ? array.clone() : array; } + /** + * Clones an array if it is not null and has a length greater than 0. Otherwise, returns the + * array. + */ + public static int[] cloneIfNotEmpty(int[] array) { + return ((array != null) && (array.length > 0)) ? array.clone() : array; + } + public static byte[] cloneIfNotEmpty(byte[] array) { return ((array != null) && (array.length > 0)) ? array.clone() : array; } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index c2a7b2ee6323..e92eaca2b6e9 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -267,6 +267,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mUserPresenceRequired; private final byte[] mAttestationChallenge; private final boolean mDevicePropertiesAttestationIncluded; + private final int[] mAttestationIds; private final boolean mUniqueIdIncluded; private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mInvalidatedByBiometricEnrollment; @@ -308,6 +309,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean userPresenceRequired, byte[] attestationChallenge, boolean devicePropertiesAttestationIncluded, + int[] attestationIds, boolean uniqueIdIncluded, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, @@ -361,6 +363,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationType = userAuthenticationType; mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge); mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded; + mAttestationIds = attestationIds; mUniqueIdIncluded = uniqueIdIncluded; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; @@ -720,6 +723,25 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @hide + * Allows the caller to specify device IDs to be attested to in the certificate for the + * generated key pair. These values are the enums specified in + * {@link android.security.keystore.AttestationUtils} + * + * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL + * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI + * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID + * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION + * + * @return integer array representing the requested device IDs to attest. + */ + @SystemApi + @Nullable + public int[] getAttestationIds() { + return Utils.cloneIfNotNull(mAttestationIds); + } + + /** * @hide This is a system-only API * * Returns {@code true} if the attestation certificate will contain a unique ID field. @@ -834,6 +856,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mUserPresenceRequired = false; private byte[] mAttestationChallenge = null; private boolean mDevicePropertiesAttestationIncluded = false; + private int[] mAttestationIds = null; private boolean mUniqueIdIncluded = false; private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; @@ -902,6 +925,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mAttestationChallenge = sourceSpec.getAttestationChallenge(); mDevicePropertiesAttestationIncluded = sourceSpec.isDevicePropertiesAttestationIncluded(); + mAttestationIds = sourceSpec.getAttestationIds(); mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded(); mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody(); mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment(); @@ -1473,6 +1497,26 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @hide + * Sets which IDs to attest in the attestation certificate for the key. The acceptable + * values in this integer array are the enums specified in + * {@link android.security.keystore.AttestationUtils} + * + * @param attestationIds the array of ID types to attest to in the certificate. + * + * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL + * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI + * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID + * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION + */ + @SystemApi + @NonNull + public Builder setAttestationIds(@NonNull int[] attestationIds) { + mAttestationIds = attestationIds; + return this; + } + + /** * @hide Only system apps can use this method. * * Sets whether to include a temporary unique ID field in the attestation certificate. @@ -1638,6 +1682,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserPresenceRequired, mAttestationChallenge, mDevicePropertiesAttestationIncluded, + mAttestationIds, mUniqueIdIncluded, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 8163472abdfb..1f2f853b67a8 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -101,6 +101,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isUserPresenceRequired()); out.writeByteArray(mSpec.getAttestationChallenge()); out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded()); + out.writeIntArray(mSpec.getAttestationIds()); out.writeBoolean(mSpec.isUniqueIdIncluded()); out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody()); out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment()); @@ -160,6 +161,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean userPresenceRequired = in.readBoolean(); final byte[] attestationChallenge = in.createByteArray(); final boolean devicePropertiesAttestationIncluded = in.readBoolean(); + final int[] attestationIds = in.createIntArray(); final boolean uniqueIdIncluded = in.readBoolean(); final boolean userAuthenticationValidWhileOnBody = in.readBoolean(); final boolean invalidatedByBiometricEnrollment = in.readBoolean(); @@ -195,6 +197,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { userPresenceRequired, attestationChallenge, devicePropertiesAttestationIncluded, + attestationIds, uniqueIdIncluded, userAuthenticationValidWhileOnBody, invalidatedByBiometricEnrollment, diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java index 5722c7b53ef4..e58b1ccb5370 100644 --- a/keystore/java/android/security/keystore/Utils.java +++ b/keystore/java/android/security/keystore/Utils.java @@ -33,4 +33,8 @@ abstract class Utils { static byte[] cloneIfNotNull(byte[] value) { return (value != null) ? value.clone() : null; } + + static int[] cloneIfNotNull(int[] value) { + return (value != null) ? value.clone() : null; + } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 70e30d2de5a1..4d27c3454a84 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -18,16 +18,20 @@ package android.security.keystore2; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.SecurityLevel; import android.os.Build; import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore; import android.security.KeyStore2; import android.security.KeyStoreException; import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.ArrayUtils; +import android.security.keystore.AttestationUtils; +import android.security.keystore.DeviceIdAttestationException; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeymasterUtils; @@ -38,6 +42,8 @@ import android.system.keystore2.IKeystoreSecurityLevel; import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyMetadata; import android.system.keystore2.ResponseCode; +import android.telephony.TelephonyManager; +import android.util.ArraySet; import android.util.Log; import libcore.util.EmptyArray; @@ -478,7 +484,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } throw p; } - } catch (UnrecoverableKeyException e) { + } catch (UnrecoverableKeyException | IllegalArgumentException + | DeviceIdAttestationException e) { throw new ProviderException( "Failed to construct key object from newly generated key pair.", e); } finally { @@ -496,7 +503,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } private void addAttestationParameters(@NonNull List<KeyParameter> params) - throws ProviderException { + throws ProviderException, IllegalArgumentException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); if (challenge != null) { @@ -526,15 +533,69 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato Build.MODEL.getBytes(StandardCharsets.UTF_8) )); } - } else { - if (mSpec.isDevicePropertiesAttestationIncluded()) { - throw new ProviderException("An attestation challenge must be provided when " - + "requesting device properties attestation."); + + int[] idTypes = mSpec.getAttestationIds(); + if (idTypes == null) { + return; + } + final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); + for (int idType : idTypes) { + idTypesSet.add(idType); + } + TelephonyManager telephonyService = null; + if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI) + || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) { + telephonyService = + (TelephonyManager) KeyStore.getApplicationContext().getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyService == null) { + throw new DeviceIdAttestationException("Unable to access telephony service"); + } + } + for (final Integer idType : idTypesSet) { + switch (idType) { + case AttestationUtils.ID_TYPE_SERIAL: + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, + Build.getSerial().getBytes(StandardCharsets.UTF_8) + )); + break; + case AttestationUtils.ID_TYPE_IMEI: { + final String imei = telephonyService.getImei(0); + if (imei == null) { + throw new DeviceIdAttestationException("Unable to retrieve IMEI"); + } + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, + imei.getBytes(StandardCharsets.UTF_8) + )); + break; + } + case AttestationUtils.ID_TYPE_MEID: { + final String meid = telephonyService.getMeid(0); + if (meid == null) { + throw new DeviceIdAttestationException("Unable to retrieve MEID"); + } + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, + meid.getBytes(StandardCharsets.UTF_8) + )); + break; + } + case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: { + params.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION)); + break; + } + default: + throw new IllegalArgumentException("Unknown device ID type " + idType); + } } } } - private Collection<KeyParameter> constructKeyGenerationArguments() { + private Collection<KeyParameter> constructKeyGenerationArguments() + throws DeviceIdAttestationException, IllegalArgumentException { List<KeyParameter> params = new ArrayList<>(); params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits)); params.add(KeyStore2ParameterUtils.makeEnum( diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java index 0bf6965beb5f..5c91cf41bfc6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java @@ -72,7 +72,7 @@ class SampleExtensionImpl extends StubExtension implements private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { List<ExtensionDisplayFeature> features = new ArrayList<>(); - int displayId = activity.getDisplayId(); + int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return features; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index 1094a0e2b4da..d3700f88d97f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -84,7 +84,7 @@ class SampleSidecarImpl extends StubSidecar implements private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>(); - int displayId = activity.getDisplayId(); + int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return features; diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml new file mode 100644 index 000000000000..cd3153145be3 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml @@ -0,0 +1,30 @@ +<?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. + --> +<com.android.wm.shell.sizecompatui.SizeCompatRestartButton + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ImageButton + android:id="@+id/size_compat_restart_button" + android:layout_width="@dimen/size_compat_button_size" + android:layout_height="@dimen/size_compat_button_size" + android:layout_gravity="center" + android:src="@drawable/size_compat_restart_button" + android:contentDescription="@string/restart_button_description"/> + +</com.android.wm.shell.sizecompatui.SizeCompatRestartButton> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 13f1fddfdfb6..24198659e15d 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -48,4 +48,7 @@ <!-- one handed background panel default alpha --> <item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item> + + <!-- maximum animation duration for the icon when entering the starting window --> + <integer name="max_starting_window_intro_icon_anim_duration">1000</integer> </resources> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 034e65c608a3..583964b2f4a4 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -173,4 +173,13 @@ <!-- The width/height of the icon view on staring surface. --> <dimen name="starting_surface_icon_size">108dp</dimen> + + <!-- The width/height of the size compat restart button. --> + <dimen name="size_compat_button_size">48dp</dimen> + + <!-- The width of the brand image on staring surface. --> + <dimen name="starting_surface_brand_image_width">200dp</dimen> + + <!-- The height of the brand image on staring surface. --> + <dimen name="starting_surface_brand_image_height">80dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java index afe523af7cb0..6984ea458ccf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -95,9 +95,16 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (!mLeashByTaskId.contains(taskId)) { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + b.setParent(mLeashByTaskId.get(taskId)); + } + + @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; pw.println(prefix + this); pw.println(innerPrefix + mLeashByTaskId.size() + " Tasks"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index b22f358c0781..efc55c4fbe31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -25,6 +25,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.os.Binder; @@ -38,9 +40,6 @@ import android.window.StartingWindowInfo; import android.window.TaskAppearedInfo; import android.window.TaskOrganizer; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; @@ -83,6 +82,16 @@ public class ShellTaskOrganizer extends TaskOrganizer { default void onTaskInfoChanged(RunningTaskInfo taskInfo) {} default void onTaskVanished(RunningTaskInfo taskInfo) {} default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {} + /** Whether this task listener supports size compat UI. */ + default boolean supportSizeCompatUI() { + // All TaskListeners should support size compat except PIP. + return true; + } + /** Attaches the a child window surface to the task surface. */ + default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + throw new IllegalStateException( + "This task listener doesn't support child surface attachment."); + } default void dump(@NonNull PrintWriter pw, String prefix) {}; } @@ -111,23 +120,23 @@ public class ShellTaskOrganizer extends TaskOrganizer { private final SizeCompatUIController mSizeCompatUI; public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { - this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */); + this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */, + new StartingSurfaceDrawer(context, mainExecutor)); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable SizeCompatUIController sizeCompatUI) { - this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI); + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + new StartingSurfaceDrawer(context, mainExecutor)); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable SizeCompatUIController sizeCompatUI) { + Context context, @Nullable SizeCompatUIController sizeCompatUI, + StartingSurfaceDrawer startingSurfaceDrawer) { super(taskOrganizerController, mainExecutor); - // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled - // by a controller, that class should be create while porting - // ActivityRecord#addStartingWindow to WMShell. - mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor); mSizeCompatUI = sizeCompatUI; + mStartingSurfaceDrawer = startingSurfaceDrawer; } @Override @@ -254,6 +263,11 @@ public class ShellTaskOrganizer extends TaskOrganizer { } @Override + public void copySplashScreenView(int taskId) { + mStartingSurfaceDrawer.copySplashScreenView(taskId); + } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mLock) { onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); @@ -354,8 +368,10 @@ public class ShellTaskOrganizer extends TaskOrganizer { return; } - // The task is vanished, notify to remove size compat UI on this Task if there is any. - if (taskListener == null) { + // The task is vanished or doesn't support size compat UI, notify to remove size compat UI + // on this Task if there is any. + if (taskListener == null || !taskListener.supportSizeCompatUI() + || !taskInfo.topActivityInSizeCompat) { mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */); @@ -363,10 +379,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { } mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, - taskInfo.configuration.windowConfiguration.getBounds(), - // null if the top activity not in size compat. - taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null, - taskListener); + taskInfo.configuration, taskInfo.topActivityToken, taskListener); } private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index bb8a97344664..5992447bd6da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -301,6 +301,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (mTaskInfo.taskId != taskId) { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + b.setParent(mTaskLeash); + } + + @Override public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index bab5140e2f52..79f9dcd8a1fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -214,6 +214,19 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (getRootTaskId() == taskId) { + b.setParent(mRootTaskLeash); + } else if (getTaskId1() == taskId) { + b.setParent(mTaskLeash1); + } else if (getTaskId2() == taskId) { + b.setParent(mTaskLeash2); + } else { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + } + + @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 2391a0874bcb..047df5ba7ca9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -86,6 +86,7 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -586,11 +587,15 @@ public class BubbleController { // There were no bubbles saved for this used. return; } - for (BubbleEntry e : mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys)) { - if (canLaunchInActivityView(mContext, e)) { - updateBubble(e, true /* suppressFlyout */, false /* showInShade */); - } - } + mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> { + mMainExecutor.execute(() -> { + for (BubbleEntry e : entries) { + if (canLaunchInActivityView(mContext, e)) { + updateBubble(e, true /* suppressFlyout */, false /* showInShade */); + } + } + }); + }); // Finally, remove the entries for this user now that bubbles are restored. mSavedBubbleKeysPerUser.remove(mCurrentUserId); } @@ -856,21 +861,24 @@ public class BubbleController { } } - private void onRankingUpdated(RankingMap rankingMap) { + private void onRankingUpdated(RankingMap rankingMap, + HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) { if (mTmpRanking == null) { mTmpRanking = new NotificationListenerService.Ranking(); } String[] orderedKeys = rankingMap.getOrderedKeys(); for (int i = 0; i < orderedKeys.length; i++) { String key = orderedKeys[i]; - BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(key); + Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key); + BubbleEntry entry = entryData.first; + boolean shouldBubbleUp = entryData.second; rankingMap.getRanking(key, mTmpRanking); boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); if (isActiveBubble && !mTmpRanking.canBubble()) { // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason. // This means that the app or channel's ability to bubble has been revoked. mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED); - } else if (isActiveBubble && !mSysuiProxy.shouldBubbleUp(key)) { + } else if (isActiveBubble && !shouldBubbleUp) { // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it. // This happens when DND is enabled and configured to hide bubbles. Dismissing with // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that @@ -919,17 +927,20 @@ public class BubbleController { private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { Objects.requireNonNull(b); b.setIsBubble(isBubble); - final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(b.getKey()); - if (entry != null) { - // Updating the entry to be a bubble will trigger our normal update flow - setIsBubble(entry, isBubble, b.shouldAutoExpand()); - } else if (isBubble) { - // If bubble doesn't exist, it's a persisted bubble so we need to add it to the - // stack ourselves - Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); - inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, - !bubble.shouldAutoExpand() /* showInShade */); - } + mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> { + mMainExecutor.execute(() -> { + if (entry != null) { + // Updating the entry to be a bubble will trigger our normal update flow + setIsBubble(entry, isBubble, b.shouldAutoExpand()); + } else if (isBubble) { + // If bubble doesn't exist, it's a persisted bubble so we need to add it to the + // stack ourselves + Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); + inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, + !bubble.shouldAutoExpand() /* showInShade */); + } + }); + }); } @SuppressWarnings("FieldCanBeLocal") @@ -992,14 +1003,17 @@ public class BubbleController { } } - final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(bubble.getKey()); - if (entry != null) { - final String groupKey = entry.getStatusBarNotification().getGroupKey(); - if (getBubblesInGroup(groupKey).isEmpty()) { - // Time to potentially remove the summary - mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey()); - } - } + mSysuiProxy.getPendingOrActiveEntry(bubble.getKey(), (entry) -> { + mMainExecutor.execute(() -> { + if (entry != null) { + final String groupKey = entry.getStatusBarNotification().getGroupKey(); + if (getBubblesInGroup(groupKey).isEmpty()) { + // Time to potentially remove the summary + mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey()); + } + } + }); + }); } mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository); @@ -1121,23 +1135,6 @@ public class BubbleController { mStackView.updateContentDescription(); } - /** - * The task id of the expanded view, if the stack is expanded and not occluded by the - * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}. - */ - private int getExpandedTaskId() { - if (mStackView == null) { - return INVALID_TASK_ID; - } - final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble(); - if (expandedViewProvider != null && isStackExpanded() - && !mStackView.isExpansionAnimating() - && !mSysuiProxy.isNotificationShadeExpand()) { - return expandedViewProvider.getTaskId(); - } - return INVALID_TASK_ID; - } - @VisibleForTesting public BubbleStackView getStackView() { return mStackView; @@ -1343,9 +1340,10 @@ public class BubbleController { } @Override - public void onRankingUpdated(RankingMap rankingMap) { + public void onRankingUpdated(RankingMap rankingMap, + HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) { mMainExecutor.execute(() -> { - BubbleController.this.onRankingUpdated(rankingMap); + BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 6a1026bb24fe..8e061e9c9874 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.Looper; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; +import android.util.Pair; import android.view.View; import androidx.annotation.IntDef; @@ -37,6 +38,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.util.HashMap; import java.util.List; import java.util.concurrent.Executor; import java.util.function.BiConsumer; @@ -182,8 +184,11 @@ public interface Bubbles { * permissions on the notification channel or the global setting. * * @param rankingMap the updated ranking map from NotificationListenerService + * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should + * bubble up */ - void onRankingUpdated(RankingMap rankingMap); + void onRankingUpdated(RankingMap rankingMap, + HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey); /** * Called when the status bar has become visible or invisible (either permanently or @@ -243,14 +248,10 @@ public interface Bubbles { /** Callback to tell SysUi components execute some methods. */ interface SysuiProxy { - @Nullable - BubbleEntry getPendingOrActiveEntry(String key); + void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback); - List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys); - - boolean isNotificationShadeExpand(); - - boolean shouldBubbleUp(String key); + void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys, + Consumer<List<BubbleEntry>> callback); void setNotificationInterruption(String key); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java index 5a493c234ce3..05526018d73f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java @@ -323,6 +323,14 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { } @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (!mLeashByTaskId.contains(taskId)) { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + b.setParent(mLeashByTaskId.get(taskId)); + } + + @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java index 94c6f018b6ac..c8f89876222e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java @@ -39,7 +39,6 @@ import android.view.WindowManagerGlobal; import android.window.TaskOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowOrganizer; import com.android.internal.annotations.GuardedBy; import com.android.wm.shell.common.SyncTransactionQueue; @@ -116,7 +115,7 @@ class WindowManagerProxy { void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) { WindowContainerTransaction t = new WindowContainerTransaction(); splitLayout.resizeSplits(position, t); - new WindowOrganizer().applyTransaction(t); + applySyncTransaction(t); } boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 3064af6f5170..71331dfcef44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -66,7 +66,6 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.transition.Transitions; @@ -585,6 +584,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } @Override + public boolean supportSizeCompatUI() { + // PIP doesn't support size compat. + return false; + } + + @Override public void onFixedRotationStarted(int displayId, int newRotation) { mNextRotation = newRotation; mWaitForFixedRotation = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 53571ff70c6f..1ef9ffa494f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -558,8 +558,8 @@ public class PipResizeGestureHandler { || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y); } - mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, - mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds())); + final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds); + mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, -mAngle, callback); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java index e47e1ac71c73..9094d7de8d63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java @@ -16,101 +16,80 @@ package com.android.wm.shell.sizecompatui; -import android.app.ActivityClient; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; -import android.graphics.PixelFormat; -import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.RippleDrawable; -import android.os.IBinder; -import android.util.Log; -import android.view.Gravity; +import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.PopupWindow; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; /** Button to restart the size compat activity. */ -class SizeCompatRestartButton extends ImageButton implements View.OnClickListener, +public class SizeCompatRestartButton extends FrameLayout implements View.OnClickListener, View.OnLongClickListener { - private static final String TAG = "SizeCompatRestartButton"; - - final WindowManager.LayoutParams mWinParams; - final boolean mShouldShowHint; - final int mDisplayId; - final int mPopupOffsetX; - final int mPopupOffsetY; - private IBinder mLastActivityToken; - private PopupWindow mShowingHint; + private SizeCompatUILayout mLayout; + private ImageButton mRestartButton; + @VisibleForTesting + PopupWindow mShowingHint; + private WindowManager.LayoutParams mWinParams; - SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) { + public SizeCompatRestartButton(@NonNull Context context) { super(context); - mDisplayId = displayId; - mShouldShowHint = !hasShownHint; - final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button); - setImageDrawable(drawable); - setContentDescription(context.getString(R.string.restart_button_description)); + } - final int drawableW = drawable.getIntrinsicWidth(); - final int drawableH = drawable.getIntrinsicHeight(); - mPopupOffsetX = drawableW / 2; - mPopupOffsetY = drawableH * 2; + public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } - final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY); - final GradientDrawable mask = new GradientDrawable(); - mask.setShape(GradientDrawable.OVAL); - mask.setColor(color); - setBackground(new RippleDrawable(color, null /* content */, mask)); - setOnClickListener(this); - setOnLongClickListener(this); - - mWinParams = new WindowManager.LayoutParams(); - mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection()); - mWinParams.width = drawableW * 2; - mWinParams.height = drawableH * 2; - mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - mWinParams.format = PixelFormat.TRANSLUCENT; - mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName() - + context.getDisplayId()); + public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); } - void updateLastTargetActivity(IBinder activityToken) { - mLastActivityToken = activityToken; + public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } - /** @return {@code false} if the target display is invalid. */ - boolean show() { - try { - getContext().getSystemService(WindowManager.class).addView(this, mWinParams); - } catch (WindowManager.InvalidDisplayException e) { - // The target display may have been removed when the callback has just arrived. - Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e); - return false; - } - return true; + void inject(SizeCompatUILayout layout) { + mLayout = layout; + mWinParams = layout.getWindowLayoutParams(); } void remove() { - if (mShowingHint != null) { - mShowingHint.dismiss(); - } - getContext().getSystemService(WindowManager.class).removeViewImmediate(this); + dismissHint(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mRestartButton = findViewById(R.id.size_compat_restart_button); + final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY); + final GradientDrawable mask = new GradientDrawable(); + mask.setShape(GradientDrawable.OVAL); + mask.setColor(color); + mRestartButton.setBackground(new RippleDrawable(color, null /* content */, mask)); + mRestartButton.setOnClickListener(this); + mRestartButton.setOnLongClickListener(this); } @Override public void onClick(View v) { - ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken); + mLayout.onRestartButtonClicked(); } @Override @@ -122,20 +101,26 @@ class SizeCompatRestartButton extends ImageButton implements View.OnClickListene @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (mShouldShowHint) { + if (mLayout.mShouldShowHint) { + mLayout.mShouldShowHint = false; showHint(); } } @Override + public void setVisibility(@Visibility int visibility) { + if (visibility == View.GONE && mShowingHint != null) { + // Also dismiss the popup. + dismissHint(); + } + super.setVisibility(visibility); + } + + @Override public void setLayoutDirection(int layoutDirection) { - final int gravity = getGravity(layoutDirection); + final int gravity = SizeCompatUILayout.getGravity(layoutDirection); if (mWinParams.gravity != gravity) { mWinParams.gravity = gravity; - if (mShowingHint != null) { - mShowingHint.dismiss(); - showHint(); - } getContext().getSystemService(WindowManager.class).updateViewLayout(this, mWinParams); } @@ -147,8 +132,10 @@ class SizeCompatRestartButton extends ImageButton implements View.OnClickListene return; } + // TODO: popup is not attached to the button surface. Need to handle this differently for + // non-fullscreen task. final View popupView = LayoutInflater.from(getContext()).inflate( - R.layout.size_compat_mode_hint, null /* root */); + R.layout.size_compat_mode_hint, null); final PopupWindow popupWindow = new PopupWindow(popupView, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); popupWindow.setWindowLayoutType(mWinParams.type); @@ -161,12 +148,15 @@ class SizeCompatRestartButton extends ImageButton implements View.OnClickListene final Button gotItButton = popupView.findViewById(R.id.got_it); gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY), null /* content */, null /* mask */)); - gotItButton.setOnClickListener(view -> popupWindow.dismiss()); - popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY); + gotItButton.setOnClickListener(view -> dismissHint()); + popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX, + mLayout.mPopupOffsetY); } - private static int getGravity(int layoutDirection) { - return Gravity.BOTTOM - | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END); + void dismissHint() { + if (mShowingHint != null) { + mShowingHint.dismiss(); + mShowingHint = null; + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java index 48ee86c4954f..a3880f497ff3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java @@ -18,134 +18,166 @@ package com.android.wm.shell.sizecompatui; import android.annotation.Nullable; import android.content.Context; -import android.graphics.Rect; +import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.view.Display; -import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; /** - * Shows a restart-activity button on Task when the foreground activity is in size compatibility - * mode. + * Controls to show/update restart-activity buttons on Tasks based on whether the foreground + * activities are in size compatibility mode. */ public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener, DisplayImeController.ImePositionProcessor { - private static final String TAG = "SizeCompatUI"; + private static final String TAG = "SizeCompatUIController"; + + /** Whether the IME is shown on display id. */ + private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1); /** The showing buttons by task id. */ - private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1); + private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0); + /** Avoid creating display context frequently for non-default display. */ private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); - @VisibleForTesting private final Context mContext; - private final ShellExecutor mMainExecutor; private final DisplayController mDisplayController; private final DisplayImeController mImeController; + private final SyncTransactionQueue mSyncQueue; /** Only show once automatically in the process life. */ private boolean mHasShownHint; - @VisibleForTesting public SizeCompatUIController(Context context, DisplayController displayController, DisplayImeController imeController, - ShellExecutor mainExecutor) { + SyncTransactionQueue syncQueue) { mContext = context; - mMainExecutor = mainExecutor; mDisplayController = displayController; mImeController = imeController; + mSyncQueue = syncQueue; mDisplayController.addDisplayWindowListener(this); mImeController.addPositionProcessor(this); } - public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, - @Nullable IBinder sizeCompatActivity, + /** + * Called when the Task info changed. Creates and updates the restart button if there is an + * activity in size compat, or removes the restart button if there is no size compat activity. + * + * @param displayId display the task and activity are in. + * @param taskId task the activity is in. + * @param taskConfig task config to place the restart button with. + * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the + * top activity in this Task is not in size compat. + * @param taskListener listener to handle the Task Surface placement. + */ + public void onSizeCompatInfoChanged(int displayId, int taskId, + @Nullable Configuration taskConfig, @Nullable IBinder sizeCompatActivity, @Nullable ShellTaskOrganizer.TaskListener taskListener) { - // TODO Draw button on Task surface - if (taskBounds == null || sizeCompatActivity == null || taskListener == null) { + if (taskConfig == null || sizeCompatActivity == null || taskListener == null) { // Null token means the current foreground activity is not in size compatibility mode. - removeRestartButton(taskId); + removeLayout(taskId); + } else if (mActiveLayouts.contains(taskId)) { + // Button already exists, update the button layout. + updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener); } else { - updateRestartButton(displayId, taskId, sizeCompatActivity); + // Create a new restart button. + createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener); } } @Override public void onDisplayRemoved(int displayId) { mDisplayContextCache.remove(displayId); - for (int i = 0; i < mActiveButtons.size(); i++) { - final int taskId = mActiveButtons.keyAt(i); - final SizeCompatRestartButton button = mActiveButtons.get(taskId); - if (button != null && button.mDisplayId == displayId) { - removeRestartButton(taskId); - } + + // Remove all buttons on the removed display. + final List<Integer> toRemoveTaskIds = new ArrayList<>(); + forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId())); + for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) { + removeLayout(toRemoveTaskIds.get(i)); } } @Override - public void onImeVisibilityChanged(int displayId, boolean isShowing) { - final int newVisibility = isShowing ? View.GONE : View.VISIBLE; - for (int i = 0; i < mActiveButtons.size(); i++) { - final int taskId = mActiveButtons.keyAt(i); - final SizeCompatRestartButton button = mActiveButtons.get(taskId); - if (button == null || button.mDisplayId != displayId) { - continue; - } + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); + forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout)); + } - // Hide the button when input method is showing. - if (button.getVisibility() != newVisibility) { - button.setVisibility(newVisibility); - } + @Override + public void onImeVisibilityChanged(int displayId, boolean isShowing) { + if (isShowing) { + mDisplaysWithIme.add(displayId); + } else { + mDisplaysWithIme.remove(displayId); } + + // Hide the button when input method is showing. + forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing)); } - private void updateRestartButton(int displayId, int taskId, IBinder activityToken) { - SizeCompatRestartButton restartButton = mActiveButtons.get(taskId); - if (restartButton != null) { - restartButton.updateLastTargetActivity(activityToken); - return; - } + private boolean isImeShowingOnDisplay(int displayId) { + return mDisplaysWithIme.contains(displayId); + } + private void createLayout(int displayId, int taskId, Configuration taskConfig, + IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener) { final Context context = getOrCreateDisplayContext(displayId); if (context == null) { - Log.i(TAG, "Cannot get context for display " + displayId); + Log.e(TAG, "Cannot get context for display " + displayId); return; } - restartButton = createRestartButton(context, displayId); - restartButton.updateLastTargetActivity(activityToken); - if (restartButton.show()) { - mActiveButtons.append(taskId, restartButton); - } else { - onDisplayRemoved(displayId); - } + final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig, + activityToken, taskListener); + mActiveLayouts.put(taskId, layout); + layout.createSizeCompatButton(isImeShowingOnDisplay(displayId)); } @VisibleForTesting - SizeCompatRestartButton createRestartButton(Context context, int displayId) { - final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId, + SizeCompatUILayout createLayout(Context context, int displayId, int taskId, + Configuration taskConfig, IBinder activityToken, + ShellTaskOrganizer.TaskListener taskListener) { + final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, context, taskConfig, + taskId, activityToken, taskListener, mDisplayController.getDisplayLayout(displayId), mHasShownHint); // Only show hint for the first time. mHasShownHint = true; - return button; + return layout; } - private void removeRestartButton(int taskId) { - final SizeCompatRestartButton button = mActiveButtons.get(taskId); - if (button != null) { - button.remove(); - mActiveButtons.remove(taskId); + private void updateLayout(int taskId, Configuration taskConfig, + IBinder sizeCompatActivity, + ShellTaskOrganizer.TaskListener taskListener) { + final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + if (layout == null) { + return; + } + layout.updateSizeCompatInfo(taskConfig, sizeCompatActivity, taskListener, + isImeShowingOnDisplay(layout.getDisplayId())); + } + + private void removeLayout(int taskId) { + final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + if (layout != null) { + layout.release(); + mActiveLayouts.remove(taskId); } } @@ -167,4 +199,14 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang } return context; } + + private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) { + for (int i = 0; i < mActiveLayouts.size(); i++) { + final int taskId = mActiveLayouts.keyAt(i); + final SizeCompatUILayout layout = mActiveLayouts.get(taskId); + if (layout != null && layout.getDisplayId() == displayId) { + callback.accept(layout); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java new file mode 100644 index 000000000000..5924b53f822c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java @@ -0,0 +1,235 @@ +/* + * 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.wm.shell.sizecompatui; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import android.annotation.Nullable; +import android.app.ActivityClient; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Binder; +import android.os.IBinder; +import android.view.Gravity; +import android.view.SurfaceControl; +import android.view.View; +import android.view.WindowManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * Records and handles layout of size compat UI on a task with size compat activity. Helps to + * calculate proper bounds when configuration or button position changes. + */ +class SizeCompatUILayout { + private static final String TAG = "SizeCompatUILayout"; + + private final SyncTransactionQueue mSyncQueue; + private Context mContext; + private Configuration mTaskConfig; + private final int mDisplayId; + private final int mTaskId; + private IBinder mActivityToken; + private ShellTaskOrganizer.TaskListener mTaskListener; + private DisplayLayout mDisplayLayout; + @VisibleForTesting + final SizeCompatUIWindowManager mWindowManager; + + @VisibleForTesting + @Nullable + SizeCompatRestartButton mButton; + final int mButtonSize; + final int mPopupOffsetX; + final int mPopupOffsetY; + boolean mShouldShowHint; + + SizeCompatUILayout(SyncTransactionQueue syncQueue, Context context, Configuration taskConfig, + int taskId, IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener, + DisplayLayout displayLayout, boolean hasShownHint) { + mSyncQueue = syncQueue; + mContext = context.createConfigurationContext(taskConfig); + mTaskConfig = taskConfig; + mDisplayId = mContext.getDisplayId(); + mTaskId = taskId; + mActivityToken = activityToken; + mTaskListener = taskListener; + mDisplayLayout = displayLayout; + mShouldShowHint = !hasShownHint; + mWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this); + + mButtonSize = + mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size); + mPopupOffsetX = mButtonSize / 4; + mPopupOffsetY = mButtonSize; + } + + /** Creates the button window. */ + void createSizeCompatButton(boolean isImeShowing) { + if (isImeShowing || mButton != null) { + // When ime is showing, wait until ime is dismiss to create UI. + return; + } + mButton = mWindowManager.createSizeCompatUI(); + updateSurfacePosition(); + } + + /** Releases the button window. */ + void release() { + mButton.remove(); + mButton = null; + mWindowManager.release(); + } + + /** Called when size compat info changed. */ + void updateSizeCompatInfo(Configuration taskConfig, IBinder activityToken, + ShellTaskOrganizer.TaskListener taskListener, boolean isImeShowing) { + final Configuration prevTaskConfig = mTaskConfig; + final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener; + mTaskConfig = taskConfig; + mActivityToken = activityToken; + mTaskListener = taskListener; + + // Update configuration. + mContext = mContext.createConfigurationContext(taskConfig); + mWindowManager.setConfiguration(taskConfig); + + if (mButton == null || prevTaskListener != taskListener) { + // TaskListener changed, recreate the button for new surface parent. + release(); + createSizeCompatButton(isImeShowing); + return; + } + + if (!taskConfig.windowConfiguration.getBounds() + .equals(prevTaskConfig.windowConfiguration.getBounds())) { + // Reposition the button surface. + updateSurfacePosition(); + } + + if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) { + // Update layout for RTL. + mButton.setLayoutDirection(taskConfig.getLayoutDirection()); + updateSurfacePosition(); + } + } + + /** Called when display layout changed. */ + void updateDisplayLayout(DisplayLayout displayLayout) { + if (displayLayout == mDisplayLayout) { + return; + } + + final Rect prevStableBounds = new Rect(); + final Rect curStableBounds = new Rect(); + mDisplayLayout.getStableBounds(prevStableBounds); + displayLayout.getStableBounds(curStableBounds); + mDisplayLayout = displayLayout; + if (!prevStableBounds.equals(curStableBounds)) { + // Stable bounds changed, update button surface position. + updateSurfacePosition(); + } + } + + /** Called when IME visibility changed. */ + void updateImeVisibility(boolean isImeShowing) { + if (mButton == null) { + // Button may not be created because ime is previous showing. + createSizeCompatButton(isImeShowing); + return; + } + + final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE; + if (mButton.getVisibility() != newVisibility) { + mButton.setVisibility(newVisibility); + } + } + + /** Gets the layout params for restart button. */ + WindowManager.LayoutParams getWindowLayoutParams() { + final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( + mButtonSize, mButtonSize, + TYPE_APPLICATION_OVERLAY, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT); + winParams.gravity = getGravity(getLayoutDirection()); + winParams.token = new Binder(); + winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + mContext.getDisplayId()); + winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + return winParams; + } + + /** Called when it is ready to be placed button surface button. */ + void attachToParentSurface(SurfaceControl.Builder b) { + mTaskListener.attachChildSurfaceToTask(mTaskId, b); + } + + /** Called when the restart button is clicked. */ + void onRestartButtonClicked() { + ActivityClient.getInstance().restartActivityProcessIfVisible(mActivityToken); + } + + @VisibleForTesting + void updateSurfacePosition() { + if (mButton == null || mWindowManager.getSurfaceControl() == null) { + return; + } + // The hint popup won't be at the correct position. + mButton.dismissHint(); + + // Use stable bounds to prevent the button from overlapping with system bars. + final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); + final Rect stableBounds = new Rect(); + mDisplayLayout.getStableBounds(stableBounds); + stableBounds.intersect(taskBounds); + + // Position of the button in the container coordinate. + final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? stableBounds.left - taskBounds.left + : stableBounds.right - taskBounds.left - mButtonSize; + final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize; + + mSyncQueue.runInSync(t -> + t.setPosition(mWindowManager.getSurfaceControl(), positionX, positionY)); + } + + int getDisplayId() { + return mDisplayId; + } + + int getTaskId() { + return mTaskId; + } + + private int getLayoutDirection() { + return mContext.getResources().getConfiguration().getLayoutDirection(); + } + + static int getGravity(int layoutDirection) { + return Gravity.BOTTOM + | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java new file mode 100644 index 000000000000..a7ad982a4736 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.sizecompatui; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.view.IWindow; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceSession; +import android.view.WindowlessWindowManager; + +import com.android.wm.shell.R; + +/** + * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton}. + */ +class SizeCompatUIWindowManager extends WindowlessWindowManager { + + private Context mContext; + private final SizeCompatUILayout mLayout; + + @Nullable + private SurfaceControlViewHost mViewHost; + @Nullable + private SurfaceControl mLeash; + + SizeCompatUIWindowManager(Context context, Configuration config, SizeCompatUILayout layout) { + super(config, null /* rootSurface */, null /* hostInputToken */); + mContext = context; + mLayout = layout; + } + + @Override + public void setConfiguration(Configuration configuration) { + super.setConfiguration(configuration); + mContext = mContext.createConfigurationContext(configuration); + } + + @Override + protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. + final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) + .setContainerLayer() + .setName("SizeCompatUILeash") + .setHidden(false) + .setCallsite("SizeCompatUIWindowManager#attachToParentSurface"); + mLayout.attachToParentSurface(builder); + mLeash = builder.build(); + b.setParent(mLeash); + } + + /** Inflates {@link SizeCompatRestartButton} on to the root surface. */ + SizeCompatRestartButton createSizeCompatUI() { + if (mViewHost == null) { + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + } + + final SizeCompatRestartButton button = (SizeCompatRestartButton) + LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null); + button.inject(mLayout); + mViewHost.setView(button, mLayout.getWindowLayoutParams()); + return button; + } + + /** Releases the surface control and tears down the view hierarchy. */ + void release() { + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + + if (mLeash != null) { + new SurfaceControl.Transaction().remove(mLeash).apply(); + mLeash = null; + } + } + + /** + * Gets {@link SurfaceControl} of the surface holding size compat UI view. @return {@code null} + * if not feasible. + */ + @Nullable + SurfaceControl getSurfaceControl() { + return mLeash; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 653299326cd0..10c742b69578 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -130,6 +130,17 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (mRootTaskInfo.taskId == taskId) { + b.setParent(mRootLeash); + } else if (mChildrenLeashes.contains(taskId)) { + b.setParent(mChildrenLeashes.get(taskId)); + } else { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + } + void setBounds(Rect bounds, WindowContainerTransaction wct) { wct.setBounds(mRootTaskInfo.token, bounds); } 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 f3f2fc3686b6..45d551528940 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 @@ -31,23 +31,21 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.util.Slog; -import android.view.Gravity; -import android.view.View; -import android.view.WindowManager; -import android.widget.FrameLayout; +import android.view.Window; +import android.window.SplashScreenView; import com.android.internal.R; import com.android.internal.graphics.palette.Palette; import com.android.internal.graphics.palette.Quantizer; import com.android.internal.graphics.palette.VariationalKMeansQuantizer; -import com.android.internal.policy.PhoneWindow; import java.util.List; /** * Util class to create the view for a splash screen content. + * @hide */ -class SplashscreenContentDrawer { +public class SplashscreenContentDrawer { private static final String TAG = StartingSurfaceDrawer.TAG; private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_SPLASH_SCREEN; @@ -58,15 +56,24 @@ class SplashscreenContentDrawer { // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon. private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f); private final Context mContext; + private final int mMaxIconAnimationDuration; + private int mIconSize; + private int mBrandingImageWidth; + private int mBrandingImageHeight; - SplashscreenContentDrawer(Context context) { + SplashscreenContentDrawer(Context context, int maxIconAnimationDuration) { mContext = context; + mMaxIconAnimationDuration = maxIconAnimationDuration; } private void updateDensity() { mIconSize = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.starting_surface_icon_size); + mBrandingImageWidth = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_brand_image_width); + mBrandingImageHeight = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_brand_image_height); } private int getSystemBGColor() { @@ -83,48 +90,92 @@ class SplashscreenContentDrawer { return new ColorDrawable(getSystemBGColor()); } - View makeSplashScreenContentView(PhoneWindow win, Context context, int iconRes, + SplashScreenView makeSplashScreenContentView(Window win, Context context, int iconRes, int splashscreenContentResId) { updateDensity(); - win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); // splash screen content will be deprecated after S. - final View ssc = makeSplashscreenContentDrawable(win, context, splashscreenContentResId); + final SplashScreenView ssc = + makeSplashscreenContentDrawable(win, context, splashscreenContentResId); if (ssc != null) { return ssc; } - final TypedArray typedArray = context.obtainStyledAttributes( - com.android.internal.R.styleable.Window); - final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); - typedArray.recycle(); + final SplashScreenWindowAttrs attrs = + SplashScreenWindowAttrs.createWindowAttrs(context); + final StartingWindowViewBuilder builder = new StartingWindowViewBuilder(); final Drawable themeBGDrawable; - if (resId == 0) { + + if (attrs.mWindowBgColor != 0) { + themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor); + } else if (attrs.mWindowBgResId != 0) { + themeBGDrawable = context.getDrawable(attrs.mWindowBgResId); + } else { Slog.w(TAG, "Window background not exist!"); themeBGDrawable = createDefaultBackgroundDrawable(); + } + final int animationDuration; + final Drawable iconDrawable; + if (attrs.mReplaceIcon != null) { + iconDrawable = attrs.mReplaceIcon; + animationDuration = Math.max(0, + Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration)); } else { - themeBGDrawable = context.getDrawable(resId); + iconDrawable = iconRes != 0 ? context.getDrawable(iconRes) + : context.getPackageManager().getDefaultActivityIcon(); + animationDuration = 0; } - final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes) - : context.getPackageManager().getDefaultActivityIcon(); // TODO (b/173975965) Tracking the performance on improved splash screen. - final StartingWindowViewBuilder builder = new StartingWindowViewBuilder(); return builder - .setPhoneWindow(win) + .setWindow(win) .setContext(context) .setThemeDrawable(themeBGDrawable) - .setIconDrawable(iconDrawable).build(); + .setIconDrawable(iconDrawable) + .setIconAnimationDuration(animationDuration) + .setBrandingDrawable(attrs.mBrandingImage).build(); + } + + private static class SplashScreenWindowAttrs { + private int mWindowBgResId = 0; + private int mWindowBgColor = Color.TRANSPARENT; + private Drawable mReplaceIcon = null; + private Drawable mBrandingImage = null; + 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); + 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 { - // materials private Drawable mThemeBGDrawable; private Drawable mIconDrawable; - private PhoneWindow mPhoneWindow; + private Window mWindow; + private int mIconAnimationDuration; private Context mContext; + private Drawable mBrandingDrawable; // result private boolean mBuildComplete = false; - private View mCachedResult; + private SplashScreenView mCachedResult; private int mThemeColor; private Drawable mFinalIconDrawable; private float mScale = 1f; @@ -141,8 +192,20 @@ class SplashscreenContentDrawer { return this; } - StartingWindowViewBuilder setPhoneWindow(PhoneWindow window) { - mPhoneWindow = window; + StartingWindowViewBuilder setWindow(Window window) { + mWindow = window; + mBuildComplete = false; + return this; + } + + StartingWindowViewBuilder setIconAnimationDuration(int iconAnimationDuration) { + mIconAnimationDuration = iconAnimationDuration; + mBuildComplete = false; + return this; + } + + StartingWindowViewBuilder setBrandingDrawable(Drawable branding) { + mBrandingDrawable = branding; mBuildComplete = false; return this; } @@ -153,11 +216,11 @@ class SplashscreenContentDrawer { return this; } - View build() { + SplashScreenView build() { if (mBuildComplete) { return mCachedResult; } - if (mPhoneWindow == null || mContext == null) { + if (mWindow == null || mContext == null) { Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!"); return null; } @@ -173,7 +236,7 @@ class SplashscreenContentDrawer { mFinalIconDrawable = mIconDrawable; } final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0; - mCachedResult = fillViewWithIcon(mPhoneWindow, mContext, iconSize, mFinalIconDrawable); + mCachedResult = fillViewWithIcon(mWindow, mContext, iconSize, mFinalIconDrawable); mBuildComplete = true; return mCachedResult; } @@ -249,25 +312,26 @@ class SplashscreenContentDrawer { return true; } - private View fillViewWithIcon(PhoneWindow win, Context context, + private SplashScreenView fillViewWithIcon(Window win, Context context, int iconSize, Drawable iconDrawable) { - final StartingSurfaceWindowView surfaceWindowView = - new StartingSurfaceWindowView(context, iconSize); - surfaceWindowView.setBackground(new ColorDrawable(mThemeColor)); + final SplashScreenView.Builder builder = new SplashScreenView.Builder(context); + builder.setIconSize(iconSize).setBackgroundColor(mThemeColor); if (iconDrawable != null) { - surfaceWindowView.setIconDrawable(iconDrawable); + builder.setCenterViewDrawable(iconDrawable); } + builder.setAnimationDuration(mIconAnimationDuration); + if (mBrandingDrawable != null) { + builder.setBrandingDrawable(mBrandingDrawable, mBrandingImageWidth, + mBrandingImageHeight); + } + final SplashScreenView splashScreenView = builder.build(); if (DEBUG) { - Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + surfaceWindowView); + Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView); } - win.setContentView(surfaceWindowView); - makeSystemUIColorsTransparent(win); - return surfaceWindowView; - } - - private void makeSystemUIColorsTransparent(PhoneWindow win) { - win.setStatusBarColor(Color.TRANSPARENT); - win.setNavigationBarColor(Color.TRANSPARENT); + win.setContentView(splashScreenView); + splashScreenView.cacheRootWindow(win); + splashScreenView.makeSystemUIColorsTransparent(); + return splashScreenView; } } @@ -298,8 +362,8 @@ class SplashscreenContentDrawer { return root < 0.1; } - private static View makeSplashscreenContentDrawable(PhoneWindow win, Context ctx, - int splashscreenContentResId) { + private static SplashScreenView makeSplashscreenContentDrawable(Window win, + Context ctx, int splashscreenContentResId) { // doesn't support windowSplashscreenContent after S // TODO add an allowlist to skip some packages if needed final int targetSdkVersion = ctx.getApplicationInfo().targetSdkVersion; @@ -316,7 +380,8 @@ class SplashscreenContentDrawer { if (drawable == null) { return null; } - View view = new View(ctx); + SplashScreenView view = new SplashScreenView(ctx); + view.setNotCopyable(); view.setBackground(drawable); win.setContentView(view); return view; @@ -532,34 +597,4 @@ class SplashscreenContentDrawer { } } } - - private static class StartingSurfaceWindowView extends FrameLayout { - // TODO animate the icon view - private final View mIconView; - - StartingSurfaceWindowView(Context context, int iconSize) { - super(context); - - final boolean emptyIcon = iconSize == 0; - if (emptyIcon) { - mIconView = null; - } else { - mIconView = new View(context); - FrameLayout.LayoutParams params = - new FrameLayout.LayoutParams(iconSize, iconSize); - params.gravity = Gravity.CENTER; - addView(mIconView, params); - } - setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - - // TODO support animatable icon - void setIconDrawable(Drawable icon) { - if (mIconView != null) { - mIconView.setBackground(icon); - } - } - } } 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 f3749220d4e1..5332291fd1bd 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 @@ -43,6 +43,8 @@ import android.util.SparseArray; import android.view.Display; import android.view.View; import android.view.WindowManager; +import android.window.SplashScreenView; +import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.StartingWindowInfo; import android.window.TaskOrganizer; import android.window.TaskSnapshot; @@ -63,7 +65,6 @@ import java.util.function.Consumer; * class to remove the starting window of the Task. * @hide */ - public class StartingSurfaceDrawer { static final String TAG = StartingSurfaceDrawer.class.getSimpleName(); static final boolean DEBUG_SPLASH_SCREEN = false; @@ -81,7 +82,10 @@ public class StartingSurfaceDrawer { mContext = context; mDisplayManager = mContext.getSystemService(DisplayManager.class); mMainExecutor = mainExecutor; - mSplashscreenContentDrawer = new SplashscreenContentDrawer(context); + + final int maxIconAnimDuration = context.getResources().getInteger( + com.android.wm.shell.R.integer.max_starting_window_intro_icon_anim_duration); + mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, maxIconAnimDuration); } private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); @@ -193,9 +197,8 @@ public class StartingSurfaceDrawer { public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { final PreferredStartingTypeHelper helper = new PreferredStartingTypeHelper(windowInfo); - final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SPLASH_SCREEN) { - addSplashScreenStartingWindow(runningTaskInfo, appToken); + addSplashScreenStartingWindow(windowInfo, appToken); } else if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SNAPSHOT) { final TaskSnapshot snapshot = helper.mSnapshot; makeTaskSnapshotWindow(windowInfo, appToken, snapshot); @@ -203,11 +206,13 @@ public class StartingSurfaceDrawer { // If prefer don't show, then don't show! } - private void addSplashScreenStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) { + private void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { + final RunningTaskInfo taskInfo = windowInfo.taskInfo; final ActivityInfo activityInfo = taskInfo.topActivityInfo; if (activityInfo == null) { return; } + final int displayId = taskInfo.displayId; if (activityInfo.packageName == null) { return; @@ -222,11 +227,11 @@ public class StartingSurfaceDrawer { } Context context = mContext; - int theme = activityInfo.getThemeResource(); - if (theme == 0) { - // replace with the default theme if the application didn't set - theme = com.android.internal.R.style.Theme_DeviceDefault_DayNight; - } + // replace with the default theme if the application didn't set + final int theme = windowInfo.splashScreenThemeResId != 0 + ? windowInfo.splashScreenThemeResId + : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource() + : com.android.internal.R.style.Theme_DeviceDefault_DayNight; if (DEBUG_SPLASH_SCREEN) { Slog.d(TAG, "addSplashScreen " + activityInfo.packageName + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme=" @@ -352,9 +357,10 @@ public class StartingSurfaceDrawer { } params.setTitle("Splash Screen " + activityInfo.packageName); - final View contentView = mSplashscreenContentDrawer.makeSplashScreenContentView(win, - context, iconRes, splashscreenContentResId[0]); - if (contentView == null) { + final SplashScreenView splashScreenView = + mSplashscreenContentDrawer.makeSplashScreenContentView(win, context, iconRes, + splashscreenContentResId[0]); + if (splashScreenView == null) { Slog.w(TAG, "Adding splash screen window for " + activityInfo.packageName + " failed!"); return; } @@ -366,7 +372,7 @@ public class StartingSurfaceDrawer { + activityInfo.packageName + " / " + appToken + ": " + view); } final WindowManager wm = context.getSystemService(WindowManager.class); - postAddWindow(taskInfo.taskId, appToken, view, wm, params); + postAddWindow(taskInfo.taskId, appToken, view, wm, params, splashScreenView); } /** @@ -379,7 +385,7 @@ public class StartingSurfaceDrawer { snapshot, mMainExecutor, () -> removeWindowSynced(taskId) /* clearWindow */); mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); final StartingWindowRecord tView = - new StartingWindowRecord(null/* decorView */, surface); + new StartingWindowRecord(null/* decorView */, surface, null /* splashScreenView */); mStartingWindowRecords.put(taskId, tView); } @@ -393,37 +399,60 @@ public class StartingSurfaceDrawer { removeWindowSynced(taskId); } + /** + * Called when the Task wants to copy the splash screen. + * @param taskId + */ + public void copySplashScreenView(int taskId) { + final StartingWindowRecord preView = mStartingWindowRecords.get(taskId); + SplashScreenViewParcelable parcelable; + if (preView != null && preView.mContentView != null + && preView.mContentView.isCopyable()) { + parcelable = new SplashScreenViewParcelable(preView.mContentView); + } else { + parcelable = null; + } + if (DEBUG_SPLASH_SCREEN) { + Slog.v(TAG, "Copying splash screen window view for task: " + taskId + + " parcelable? " + parcelable); + } + ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable); + } + protected void postAddWindow(int taskId, IBinder appToken, - View view, WindowManager wm, WindowManager.LayoutParams params) { - boolean shouldSaveView = true; - try { - wm.addView(view, params); - } catch (WindowManager.BadTokenException e) { - // ignore - Slog.w(TAG, appToken + " already running, starting window not displayed. " - + e.getMessage()); - shouldSaveView = false; - } 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, appToken + " failed creating starting window", e); - shouldSaveView = false; - } finally { - if (view != null && view.getParent() == null) { - Slog.w(TAG, "view not successfully added to wm, removing view"); - wm.removeViewImmediate(view); + View view, WindowManager wm, WindowManager.LayoutParams params, + SplashScreenView splashScreenView) { + mMainExecutor.execute(() -> { + boolean shouldSaveView = true; + try { + wm.addView(view, params); + } catch (WindowManager.BadTokenException e) { + // ignore + Slog.w(TAG, appToken + " already running, starting window not displayed. " + + e.getMessage()); shouldSaveView = false; + } 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, appToken + " failed creating starting window", e); + shouldSaveView = false; + } finally { + if (view != null && view.getParent() == null) { + Slog.w(TAG, "view not successfully added to wm, removing view"); + wm.removeViewImmediate(view); + shouldSaveView = false; + } } - } - - if (shouldSaveView) { - removeWindowSynced(taskId); - mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); - final StartingWindowRecord tView = - new StartingWindowRecord(view, null /* TaskSnapshotWindow */); - mStartingWindowRecords.put(taskId, tView); - } + if (shouldSaveView) { + removeWindowSynced(taskId); + mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); + final StartingWindowRecord tView = new StartingWindowRecord(view, + null /* TaskSnapshotWindow */, splashScreenView); + splashScreenView.startIntroAnimation(); + mStartingWindowRecords.put(taskId, tView); + } + }); } protected void removeWindowSynced(int taskId) { @@ -459,10 +488,13 @@ public class StartingSurfaceDrawer { private static class StartingWindowRecord { private final View mDecorView; private final TaskSnapshotWindow mTaskSnapshotWindow; + private final SplashScreenView mContentView; - StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) { + StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow, + SplashScreenView splashScreenView) { mDecorView = decorView; mTaskSnapshotWindow = taskSnapshotWindow; + mContentView = splashScreenView; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index a6f44efd7645..b7fd3cb67b2b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -43,6 +43,7 @@ import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_AT import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.getNavigationBarRect; +import android.annotation.BinderThread; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; @@ -498,7 +499,7 @@ public class TaskSnapshotWindow { } } - @ExternalThread + @BinderThread static class Window extends BaseIWindow { private TaskSnapshotWindow mOuter; diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index 111362a93495..ecc066be734f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -24,7 +24,6 @@ import android.os.SystemClock import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE -import com.android.server.wm.flicker.helpers.closePipWindow import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild @@ -113,7 +112,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( if (isTelevision) { uiDevice.closeTvPipWindow() } else { - uiDevice.closePipWindow() + closePipWindow(WindowManagerStateHelper(mInstrumentation)) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt index a14b46ef7a3d..d56ed02972fb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt @@ -16,13 +16,11 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible @@ -50,8 +48,7 @@ class EnterExitPipTest( @JvmStatic fun getParams(): List<Array<Any>> { val testApp = FixedAppHelper(instrumentation) - val baseConfig = getTransitionLaunch(eachRun = true) - val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + val testSpec = getTransition(eachRun = true) { configuration -> setup { eachRun { testApp.launchViaIntent(wmHelper) @@ -97,7 +94,7 @@ class EnterExitPipTest( } } } - return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 99a40daa027f..ff31ba7d2c01 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -16,13 +16,11 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible @@ -50,9 +48,8 @@ class EnterPipTest( @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<Array<Any>> { - val baseConfig = getTransitionLaunch( - eachRun = true, stringExtras = emptyMap()) - val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + val testSpec = getTransition(eachRun = true, + stringExtras = emptyMap()) { configuration -> transitions { pipApp.clickEnterPipButton() pipApp.expandPipWindow(wmHelper) @@ -92,7 +89,7 @@ class EnterPipTest( } } - return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 7576e24ace19..f054e6412080 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -16,13 +16,11 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation @@ -48,8 +46,7 @@ class PipKeyboardTest(testSpec: FlickerTestRunnerFactory.TestSpec) : FlickerTest @JvmStatic fun getParams(): Collection<Array<Any>> { val imeApp = ImeAppHelper(instrumentation) - val baseConfig = getTransitionLaunch(eachRun = false) - val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + val testSpec = getTransition(eachRun = false) { configuration -> setup { test { imeApp.launchViaIntent(wmHelper) @@ -90,7 +87,7 @@ class PipKeyboardTest(testSpec: FlickerTestRunnerFactory.TestSpec) : FlickerTest } return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, - baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0), + testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index adab5e81b32d..ade65ac8aa63 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -16,13 +16,11 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation @@ -55,8 +53,7 @@ class PipRotationTest( @JvmStatic fun getParams(): Collection<Array<Any>> { val fixedApp = FixedAppHelper(instrumentation) - val baseConfig = getTransitionLaunch(eachRun = false) - val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + val testSpec = getTransition(eachRun = false) { configuration -> setup { test { fixedApp.launchViaIntent(wmHelper) @@ -112,8 +109,7 @@ class PipRotationTest( } return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation, - baseConfig, testSpec, - supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), + testSpec, supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), repetitions = 5) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt index 4b826ffd646d..f2d58997d1f2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt @@ -16,13 +16,11 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible @@ -52,8 +50,7 @@ class PipToAppTest( @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<Array<Any>> { - val baseConfig = getTransitionLaunch(eachRun = true) - val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + val testSpec = getTransition(eachRun = true) { configuration -> setup { eachRun { this.setRotation(configuration.startRotation) @@ -110,7 +107,7 @@ class PipToAppTest( } } - return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt index 62e82212b1d1..1b44377425db 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt @@ -16,13 +16,11 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible @@ -52,8 +50,7 @@ class PipToHomeTest( @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<Array<Any>> { - val baseConfig = getTransitionLaunch(eachRun = true) - val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + val testSpec = getTransition(eachRun = true) { configuration -> setup { eachRun { this.setRotation(configuration.startRotation) @@ -111,7 +108,7 @@ class PipToHomeTest( } } - return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt index eb7bae160577..b1e404e4c8e6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt @@ -22,8 +22,6 @@ import android.os.Bundle import android.view.Surface import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.buildTestTag -import com.android.server.wm.flicker.helpers.closePipWindow -import com.android.server.wm.flicker.helpers.hasPipWindow import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.repetitions @@ -83,10 +81,6 @@ abstract class PipTransitionBase(protected val instrumentation: Instrumentation) } test { removeAllTasksButHome() - - if (device.hasPipWindow()) { - device.closePipWindow() - } pipApp.exit() } } @@ -98,11 +92,13 @@ abstract class PipTransitionBase(protected val instrumentation: Instrumentation) * * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test) * @param stringExtras Arguments to pass to the PIP launch intent + * @param extraSpec Addicional segment of flicker specification */ @JvmOverloads - fun getTransitionLaunch( + open fun getTransition( eachRun: Boolean, - stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true") + stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"), + extraSpec: FlickerBuilder.(Bundle) -> Unit = {} ): FlickerBuilder.(Bundle) -> Unit { return { configuration -> setupAndTeardown(this, configuration) @@ -135,6 +131,8 @@ abstract class PipTransitionBase(protected val instrumentation: Instrumentation) removeAllTasksButHome() } } + + extraSpec(this, configuration) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 176b33dda020..a0e9f43218f2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -52,6 +53,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sizecompatui.SizeCompatUIController; +import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; import org.junit.Before; import org.junit.Test; @@ -77,6 +79,8 @@ public class ShellTaskOrganizerTests { private Context mContext; @Mock private SizeCompatUIController mSizeCompatUI; + @Mock + private StartingSurfaceDrawer mStartingSurfaceDrawer; ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); @@ -112,7 +116,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mSizeCompatUI)); + mSizeCompatUI, mStartingSurfaceDrawer)); } @Test @@ -279,10 +283,10 @@ public class ShellTaskOrganizerTests { // sizeCompatActivity is null if top activity is not in size compat. verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, - taskInfo1.configuration.windowConfiguration.getBounds(), - null /* sizeCompatActivity*/ , taskListener); + null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */); // sizeCompatActivity is non-null if top activity is in size compat. + clearInvocations(mSizeCompatUI); final RunningTaskInfo taskInfo2 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo2.displayId = taskInfo1.displayId; @@ -290,14 +294,12 @@ public class ShellTaskOrganizerTests { taskInfo2.topActivityInSizeCompat = true; mOrganizer.onTaskInfoChanged(taskInfo2); verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, - taskInfo1.configuration.windowConfiguration.getBounds(), - taskInfo1.topActivityToken, - taskListener); + taskInfo1.configuration, taskInfo1.topActivityToken, taskListener); + clearInvocations(mSizeCompatUI); mOrganizer.onTaskVanished(taskInfo1); verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, - null /* taskConfig */, null /* sizeCompatActivity*/, - null /* taskListener */); + null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */); } private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java new file mode 100644 index 000000000000..d9086a6ccdc1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java @@ -0,0 +1,115 @@ +/* + * 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.wm.shell.sizecompatui; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link SizeCompatRestartButton}. + * + * Build/Install/Run: + * atest WMShellUnitTests:SizeCompatRestartButtonTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class SizeCompatRestartButtonTest extends ShellTestCase { + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock private IBinder mActivityToken; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private DisplayLayout mDisplayLayout; + + private SizeCompatUILayout mLayout; + private SizeCompatRestartButton mButton; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final int taskId = 1; + mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(), + taskId, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/); + mButton = (SizeCompatRestartButton) + LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null); + mButton.inject(mLayout); + + spyOn(mLayout); + spyOn(mButton); + doNothing().when(mButton).showHint(); + } + + @Test + public void testOnClick() { + doNothing().when(mLayout).onRestartButtonClicked(); + + mButton.onClick(mButton); + + verify(mLayout).onRestartButtonClicked(); + } + + @Test + public void testOnLongClick() { + verify(mButton, never()).showHint(); + + mButton.onLongClick(mButton); + + verify(mButton).showHint(); + } + + @Test + public void testOnAttachedToWindow_showHint() { + mLayout.mShouldShowHint = false; + mButton.onAttachedToWindow(); + + verify(mButton, never()).showHint(); + + mLayout.mShouldShowHint = true; + mButton.onAttachedToWindow(); + + verify(mButton).showHint(); + } + + @Test + public void testRemove() { + mButton.remove(); + + verify(mButton).dismissHint(); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java index 0eb64e5963d1..806a90b7832a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java @@ -16,23 +16,28 @@ package com.android.wm.shell.sizecompatui; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.Context; -import android.graphics.Rect; +import android.content.res.Configuration; import android.os.IBinder; import android.testing.AndroidTestingRunner; -import android.view.View; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; import org.junit.Before; import org.junit.Test; @@ -50,28 +55,34 @@ import org.mockito.MockitoAnnotations; @SmallTest public class SizeCompatUIControllerTest extends ShellTestCase { private static final int DISPLAY_ID = 0; - - private final TestShellExecutor mShellMainExecutor = new TestShellExecutor(); + private static final int TASK_ID = 12; private SizeCompatUIController mController; private @Mock DisplayController mMockDisplayController; + private @Mock DisplayLayout mMockDisplayLayout; private @Mock DisplayImeController mMockImeController; - private @Mock SizeCompatRestartButton mMockButton; private @Mock IBinder mMockActivityToken; private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener; + private @Mock SyncTransactionQueue mMockSyncQueue; + private @Mock SizeCompatUILayout mMockLayout; @Before public void setUp() { MockitoAnnotations.initMocks(this); - doReturn(true).when(mMockButton).show(); + doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt()); + doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId(); + doReturn(TASK_ID).when(mMockLayout).getTaskId(); mController = new SizeCompatUIController(mContext, mMockDisplayController, - mMockImeController, mShellMainExecutor) { + mMockImeController, mMockSyncQueue) { @Override - SizeCompatRestartButton createRestartButton(Context context, int displayId) { - return mMockButton; + SizeCompatUILayout createLayout(Context context, int displayId, int taskId, + Configuration taskConfig, IBinder activityToken, + ShellTaskOrganizer.TaskListener taskListener) { + return mMockLayout; } }; + spyOn(mController); } @Test @@ -82,42 +93,72 @@ public class SizeCompatUIControllerTest extends ShellTestCase { @Test public void testOnSizeCompatInfoChanged() { - final int taskId = 12; - final Rect taskBounds = new Rect(0, 0, 1000, 2000); + final Configuration taskConfig = new Configuration(); + + // Verify that the restart button is added with non-null size compat info. + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockActivityToken, mMockTaskListener); + + verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig), + eq(mMockActivityToken), eq(mMockTaskListener)); - // Verify that the restart button is added with non-null size compat activity. - mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, + // Verify that the restart button is updated with non-null new size compat info. + final Configuration newTaskConfig = new Configuration(); + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockActivityToken, mMockTaskListener); - mShellMainExecutor.flushAll(); - verify(mMockButton).show(); - verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken)); + verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockActivityToken, mMockTaskListener, + false /* isImeShowing */); + + // Verify that the restart button is removed with null size compat info. + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, null, mMockTaskListener); + + verify(mMockLayout).release(); + } + + @Test + public void testOnDisplayRemoved() { + final Configuration taskConfig = new Configuration(); + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockActivityToken, mMockTaskListener); + + mController.onDisplayRemoved(DISPLAY_ID + 1); + + verify(mMockLayout, never()).release(); + + mController.onDisplayRemoved(DISPLAY_ID); + + verify(mMockLayout).release(); + } + + @Test + public void testOnDisplayConfigurationChanged() { + final Configuration taskConfig = new Configuration(); + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockActivityToken, mMockTaskListener); + + final Configuration newTaskConfig = new Configuration(); + mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig); + + verify(mMockLayout, never()).updateDisplayLayout(any()); - // Verify that the restart button is removed with null size compat activity. - mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null); + mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig); - mShellMainExecutor.flushAll(); - verify(mMockButton).remove(); + verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout); } @Test public void testChangeButtonVisibilityOnImeShowHide() { - final int taskId = 12; - final Rect taskBounds = new Rect(0, 0, 1000, 2000); - mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, + final Configuration taskConfig = new Configuration(); + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockActivityToken, mMockTaskListener); - mShellMainExecutor.flushAll(); - // Verify that the restart button is hidden when IME is visible. - doReturn(View.VISIBLE).when(mMockButton).getVisibility(); mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */); - verify(mMockButton).setVisibility(eq(View.GONE)); + verify(mMockLayout).updateImeVisibility(true); - // Verify that the restart button is visible when IME is hidden. - doReturn(View.GONE).when(mMockButton).getVisibility(); mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */); - verify(mMockButton).setVisibility(eq(View.VISIBLE)); + verify(mMockLayout).updateImeVisibility(false); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java new file mode 100644 index 000000000000..236db44bdce0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java @@ -0,0 +1,206 @@ +/* + * 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.wm.shell.sizecompatui; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityClient; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.IBinder; +import android.testing.AndroidTestingRunner; +import android.view.DisplayInfo; +import android.view.SurfaceControl; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link SizeCompatUILayout}. + * + * Build/Install/Run: + * atest WMShellUnitTests:SizeCompatUILayoutTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class SizeCompatUILayoutTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock private IBinder mActivityToken; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private DisplayLayout mDisplayLayout; + @Mock private SizeCompatRestartButton mButton; + private Configuration mTaskConfig; + + private SizeCompatUILayout mLayout; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTaskConfig = new Configuration(); + + mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(), + TASK_ID, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/); + + spyOn(mLayout); + spyOn(mLayout.mWindowManager); + doReturn(mButton).when(mLayout.mWindowManager).createSizeCompatUI(); + } + + @Test + public void testCreateSizeCompatButton() { + // Not create button if IME is showing. + mLayout.createSizeCompatButton(true /* isImeShowing */); + + verify(mLayout.mWindowManager, never()).createSizeCompatUI(); + assertNull(mLayout.mButton); + + mLayout.createSizeCompatButton(false /* isImeShowing */); + + verify(mLayout.mWindowManager).createSizeCompatUI(); + assertNotNull(mLayout.mButton); + } + + @Test + public void testRelease() { + mLayout.createSizeCompatButton(false /* isImeShowing */); + + mLayout.release(); + + assertNull(mLayout.mButton); + verify(mButton).remove(); + verify(mLayout.mWindowManager).release(); + } + + @Test + public void testUpdateSizeCompatInfo() { + mLayout.createSizeCompatButton(false /* isImeShowing */); + + // No diff + clearInvocations(mLayout); + mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, mTaskListener, + false /* isImeShowing */); + + verify(mLayout, never()).updateSurfacePosition(); + verify(mLayout, never()).release(); + verify(mLayout, never()).createSizeCompatButton(anyBoolean()); + + // Change task listener, recreate button. + clearInvocations(mLayout); + final ShellTaskOrganizer.TaskListener newTaskListener = mock( + ShellTaskOrganizer.TaskListener.class); + mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, newTaskListener, + false /* isImeShowing */); + + verify(mLayout).release(); + verify(mLayout).createSizeCompatButton(anyBoolean()); + + // Change task bounds, update position. + clearInvocations(mLayout); + final Configuration newTaskConfiguration = new Configuration(); + newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000)); + mLayout.updateSizeCompatInfo(newTaskConfiguration, mActivityToken, newTaskListener, + false /* isImeShowing */); + + verify(mLayout).updateSurfacePosition(); + } + + @Test + public void testUpdateDisplayLayout() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, + mContext.getResources(), false, false); + + mLayout.updateDisplayLayout(displayLayout1); + verify(mLayout).updateSurfacePosition(); + + // No update if the display bounds is the same. + clearInvocations(mLayout); + final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, + mContext.getResources(), false, false); + mLayout.updateDisplayLayout(displayLayout2); + verify(mLayout, never()).updateSurfacePosition(); + } + + @Test + public void testUpdateImeVisibility() { + // Create button if it is not created. + mLayout.mButton = null; + mLayout.updateImeVisibility(false /* isImeShowing */); + + verify(mLayout).createSizeCompatButton(false /* isImeShowing */); + + // Hide button if ime is shown. + clearInvocations(mLayout); + doReturn(View.VISIBLE).when(mButton).getVisibility(); + mLayout.updateImeVisibility(true /* isImeShowing */); + + verify(mLayout, never()).createSizeCompatButton(anyBoolean()); + verify(mButton).setVisibility(View.GONE); + + // Show button if ime is not shown. + doReturn(View.GONE).when(mButton).getVisibility(); + mLayout.updateImeVisibility(false /* isImeShowing */); + + verify(mLayout, never()).createSizeCompatButton(anyBoolean()); + verify(mButton).setVisibility(View.VISIBLE); + } + + @Test + public void testAttachToParentSurface() { + final SurfaceControl.Builder b = new SurfaceControl.Builder(); + mLayout.attachToParentSurface(b); + + verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b); + } + + @Test + public void testOnRestartButtonClicked() { + spyOn(ActivityClient.getInstance()); + doNothing().when(ActivityClient.getInstance()).restartActivityProcessIfVisible(any()); + + mLayout.onRestartButtonClicked(); + + verify(ActivityClient.getInstance()).restartActivityProcessIfVisible(mActivityToken); + } +} 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 c9537afa37ef..de7d6c74bb06 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 @@ -40,6 +40,7 @@ import android.testing.TestableContext; import android.view.View; import android.view.WindowManager; import android.view.WindowMetrics; +import android.window.SplashScreenView; import android.window.StartingWindowInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -79,7 +80,8 @@ public class StartingSurfaceDrawerTests { @Override protected void postAddWindow(int taskId, IBinder appToken, - View view, WindowManager wm, WindowManager.LayoutParams params) { + View view, WindowManager wm, WindowManager.LayoutParams params, + SplashScreenView splashScreenView) { // listen for addView mAddWindowForTask = taskId; mViewThemeResId = view.getContext().getThemeResId(); @@ -125,7 +127,8 @@ public class StartingSurfaceDrawerTests { createWindowInfo(taskId, android.R.style.Theme); mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder); waitHandlerIdle(mainLoop); - verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any()); + verify(mStartingSurfaceDrawer).postAddWindow( + eq(taskId), eq(mBinder), any(), any(), any(), any()); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId); @@ -142,7 +145,8 @@ public class StartingSurfaceDrawerTests { createWindowInfo(taskId, 0); mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder); waitHandlerIdle(mainLoop); - verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any()); + verify(mStartingSurfaceDrawer).postAddWindow( + eq(taskId), eq(mBinder), any(), any(), any(), any()); assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0); } diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 0e338f35b8e7..2db3ace1cd43 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -30,10 +30,11 @@ namespace android { -MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize, - std::string_view filePath, int ttcIndex, +MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, + size_t fontSize, std::string_view filePath, int ttcIndex, const std::vector<minikin::FontVariation>& axes) : mTypeface(std::move(typeface)) + , mSourceId(sourceId) , mFontData(fontData) , mFontSize(fontSize) , mTtcIndex(ttcIndex) @@ -141,8 +142,8 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args)); - return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, mFilePath, - ttcIndex, variations); + return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize, + mFilePath, ttcIndex, variations); } // hinting<<16 | edging<<8 | bools:5bits diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h index 77a21428f36a..de9a5c2af0aa 100644 --- a/libs/hwui/hwui/MinikinSkia.h +++ b/libs/hwui/hwui/MinikinSkia.h @@ -30,7 +30,7 @@ namespace android { class ANDROID_API MinikinFontSkia : public minikin::MinikinFont { public: - MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize, + MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize, std::string_view filePath, int ttcIndex, const std::vector<minikin::FontVariation>& axes); @@ -62,6 +62,7 @@ public: const std::vector<minikin::FontVariation>& GetAxes() const; std::shared_ptr<minikin::MinikinFont> createFontWithVariation( const std::vector<minikin::FontVariation>&) const; + int GetSourceId() const override { return mSourceId; } static uint32_t packFontFlags(const SkFont&); static void unpackFontFlags(SkFont*, uint32_t fontFlags); @@ -73,6 +74,7 @@ public: private: sk_sp<SkTypeface> mTypeface; + int mSourceId; // A raw pointer to the font data - it should be owned by some other object with // lifetime at least as long as this object. const void* mFontData; diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index 03f1d62625f1..5a9d2508230e 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -185,9 +185,9 @@ void Typeface::setRobotoTypefaceForTest() { sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData)); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont); - std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>( - std::move(typeface), data, st.st_size, kRobotoFont, 0, - std::vector<minikin::FontVariation>()); + std::shared_ptr<minikin::MinikinFont> font = + std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont, + 0, std::vector<minikin::FontVariation>()); std::vector<std::shared_ptr<minikin::Font>> fonts; fonts.push_back(minikin::Font::Builder(font).build()); diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index 2e85840cad99..ce5ac382aeff 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -17,15 +17,16 @@ #undef LOG_TAG #define LOG_TAG "Minikin" +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> +#include "FontUtils.h" +#include "GraphicsJNI.h" #include "SkData.h" #include "SkFontMgr.h" #include "SkRefCnt.h" #include "SkTypeface.h" -#include "GraphicsJNI.h" -#include <nativehelper/ScopedPrimitiveArray.h> -#include <nativehelper/ScopedUtfChars.h> #include "Utils.h" -#include "FontUtils.h" +#include "fonts/Font.h" #include <hwui/MinikinSkia.h> #include <hwui/Typeface.h> @@ -35,6 +36,12 @@ #include <memory> +/////////////////////////////////////////////////////////////////////////////////////////////////// +// +// The following JNI methods are kept only for compatibility reasons due to hidden API accesses. +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + namespace android { struct NativeFamilyBuilder { @@ -125,8 +132,8 @@ static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, in return false; } std::shared_ptr<minikin::MinikinFont> minikinFont = - std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, "", ttcIndex, - builder->axes); + std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr, + fontSize, "", ttcIndex, builder->axes); minikin::Font::Builder fontBuilder(minikinFont); if (weight != RESOLVE_BY_FONT_TABLE) { diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp index fa1752cc47d6..a48d7f734e29 100644 --- a/libs/hwui/jni/RenderEffect.cpp +++ b/libs/hwui/jni/RenderEffect.cpp @@ -64,8 +64,8 @@ static jlong createBitmapEffect( sk_sp<SkImage> image = android::bitmap::toBitmap(bitmapHandle).makeImage(); SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - sk_sp<SkImageFilter> bitmapFilter = - SkImageFilters::Image(image, srcRect, dstRect, kLow_SkFilterQuality); + sk_sp<SkImageFilter> bitmapFilter = SkImageFilters::Image( + image, srcRect, dstRect, SkSamplingOptions(SkFilterMode::kLinear)); return reinterpret_cast<jlong>(bitmapFilter.release()); } @@ -150,4 +150,4 @@ int register_android_graphics_RenderEffect(JNIEnv* env) { android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect", gRenderEffectMethods, NELEM(gRenderEffectMethods)); return 0; -}
\ No newline at end of file +} diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 1dc5cd99eed1..2e4d7f62f671 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -239,14 +239,12 @@ static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr, static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) { ScopedUtfChars strSksl(env, sksl); - auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str())); - sk_sp<SkRuntimeEffect> effect = std::get<0>(result); - if (effect.get() == nullptr) { - const auto& err = std::get<1>(result); - doThrowIAE(env, err.c_str()); + auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()), SkRuntimeEffect::Options{}); + if (result.effect.get() == nullptr) { + doThrowIAE(env, result.errorText.c_str()); return 0; } - return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(effect))); + return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(result.effect))); } static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) { diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index 18423562bc56..251323d34422 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -367,6 +367,12 @@ static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family))); } +// Regular JNI +static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) { + ScopedUtfChars filePath(env, jFilePath); + makeSkDataCached(filePath.c_str(), false /* fs verity */); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gTypefaceMethods[] = { @@ -390,6 +396,7 @@ static const JNINativeMethod gTypefaceMethods[] = { (void*)Typeface_forceSetStaticFinalField}, {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize}, {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily}, + {"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache}, }; int register_android_graphics_Typeface(JNIEnv* env) diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index c8471a9b8feb..5a972f56ea87 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -137,12 +137,9 @@ static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong sk_sp<SkTypeface> newTypeface = minikinSkia->GetSkTypeface()->makeClone(args); std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>( - std::move(newTypeface), - minikinSkia->GetFontData(), - minikinSkia->GetFontSize(), - minikinSkia->getFilePath(), - minikinSkia->GetFontIndex(), - builder->axes); + std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(), + minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(), + builder->axes); std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont) .setWeight(weight) .setSlant(static_cast<minikin::FontStyle::Slant>(italic)) @@ -279,6 +276,12 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary); } +// Critical Native +static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { + FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr); + return font->font->typeface()->GetSourceId(); +} + // Fast Native static jlong FontFileUtil_getFontRevision(JNIEnv* env, jobject, jobject buffer, jint index) { NPE_CHECK_RETURN_ZERO(env, buffer); @@ -369,6 +372,7 @@ static const JNINativeMethod gFontMethods[] = { {"nGetIndex", "(J)I", (void*)Font_getIndex}, {"nGetAxisCount", "(J)I", (void*)Font_getAxisCount}, {"nGetAxisInfo", "(JI)J", (void*)Font_getAxisInfo}, + {"nGetSourceId", "(J)I", (void*)Font_getSourceId}, }; static const JNINativeMethod gFontFileUtilMethods[] = { @@ -409,10 +413,15 @@ std::shared_ptr<minikin::MinikinFont> createMinikinFontSkia( if (face == nullptr) { return nullptr; } - return std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, + return std::make_shared<MinikinFontSkia>(std::move(face), getNewSourceId(), fontPtr, fontSize, fontPath, ttcIndex, axes); } +int getNewSourceId() { + static std::atomic<int> sSourceId = {0}; + return sSourceId++; +} + } // namespace fonts } // namespace android diff --git a/libs/hwui/jni/fonts/Font.h b/libs/hwui/jni/fonts/Font.h index b5d20bf8cc3c..4bf60ee85657 100644 --- a/libs/hwui/jni/fonts/Font.h +++ b/libs/hwui/jni/fonts/Font.h @@ -33,6 +33,8 @@ std::shared_ptr<minikin::MinikinFont> createMinikinFontSkia( sk_sp<SkData>&& data, std::string_view fontPath, const void *fontPtr, size_t fontSize, int ttcIndex, const std::vector<minikin::FontVariation>& axes); +int getNewSourceId(); + } // namespace fonts } // namespace android diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index 5d2aa2ff83c9..ab23448ab93f 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -57,7 +57,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData))); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); std::shared_ptr<minikin::MinikinFont> font = - std::make_shared<MinikinFontSkia>(std::move(typeface), data, st.st_size, fileName, 0, + std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0, std::vector<minikin::FontVariation>()); std::vector<std::shared_ptr<minikin::Font>> fonts; fonts.push_back(minikin::Font::Builder(font).build()); diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING index cf2f0f0da2b7..a7ed09118817 100644 --- a/media/TEST_MAPPING +++ b/media/TEST_MAPPING @@ -10,7 +10,7 @@ "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests" }, { - "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests" + "include-filter": "com.google.android.media.gts.WidevineH264PlaybackTests" } ] } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 1a49b85403e4..67f1660fff78 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -20,7 +20,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; -import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1154,6 +1153,22 @@ public final class MediaFormat { public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info"; /** + * An optional key describing the opto-electronic transfer function + * requested for the output video content. + * + * The associated value is an integer: 0 if unspecified, or one of the + * COLOR_TRANSFER_ values. When unspecified the component will not touch the + * video content; otherwise the component will tone-map the raw video frame + * to match the requested transfer function. + * + * After configure, component's input format will contain this key to note + * whether the request is supported or not. If the value in the input format + * is the same as the requested value, the request is supported. The value + * is set to 0 if unsupported. + */ + public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request"; + + /** * A key describing a unique ID for the content of a media track. * * <p>This key is used by {@link MediaExtractor}. Some extractors provide multiple encodings diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp index 748d45808932..359ef364083c 100644 --- a/media/jni/tuner/DemuxClient.cpp +++ b/media/jni/tuner/DemuxClient.cpp @@ -216,14 +216,13 @@ Result DemuxClient::disconnectCiCam() { Result DemuxClient::close() { if (mTunerDemux != NULL) { Status s = mTunerDemux->close(); + mDemux = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mDemux != NULL) { Result res = mDemux->close(); - if (res == Result::SUCCESS) { - mDemux = NULL; - } + mDemux = NULL; return res; } diff --git a/media/jni/tuner/DescramblerClient.cpp b/media/jni/tuner/DescramblerClient.cpp index c9bacda0fa70..07be5cf33764 100644 --- a/media/jni/tuner/DescramblerClient.cpp +++ b/media/jni/tuner/DescramblerClient.cpp @@ -101,14 +101,18 @@ Result DescramblerClient::removePid(DemuxPid pid, sp<FilterClient> optionalSourc Result DescramblerClient::close() { if (mTunerDescrambler != NULL) { Status s = mTunerDescrambler->close(); + mTunerDescrambler = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mDescrambler != NULL) { - return mDescrambler->close(); + Result res = mDescrambler->close(); + mDescrambler = NULL; + return res; } - return Result::INVALID_STATE;} + return Result::INVALID_STATE; +} /////////////// DescramblerClient Helper Methods /////////////////////// diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp index 04004858aaee..779318008a9b 100644 --- a/media/jni/tuner/DvrClient.cpp +++ b/media/jni/tuner/DvrClient.cpp @@ -316,14 +316,13 @@ Result DvrClient::flush() { Result DvrClient::close() { if (mTunerDvr != NULL) { Status s = mTunerDvr->close(); + mTunerDvr = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mDvr != NULL) { Result res = mDvr->close(); - if (res == Result::SUCCESS) { - mDvr = NULL; - } + mDvr = NULL; return res; } diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp index f61889035432..f31d4651350a 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -262,14 +262,14 @@ Result FilterClient::close() { if (mTunerFilter != NULL) { Status s = mTunerFilter->close(); closeAvSharedMemory(); + mTunerFilter = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mFilter != NULL) { Result res = mFilter->close(); - if (res == Result::SUCCESS) { - mFilter = NULL; - } + mFilter = NULL; + mFilter_1_1 = NULL; closeAvSharedMemory(); return res; } diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp index 0613223bd5dc..9e3664275dac 100644 --- a/media/jni/tuner/FrontendClient.cpp +++ b/media/jni/tuner/FrontendClient.cpp @@ -322,15 +322,14 @@ Result FrontendClient::unlinkCiCamToFrontend(int ciCamId) { Result FrontendClient::close() { if (mTunerFrontend != NULL) { Status s = mTunerFrontend->close(); + mTunerFrontend = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mFrontend != NULL) { Result result = mFrontend->close(); - if (result == Result::SUCCESS) { - mFrontend = NULL; - mFrontend_1_1 = NULL; - } + mFrontend = NULL; + mFrontend_1_1 = NULL; return result; } diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp index 8d08c2586b48..5b6e46eba418 100644 --- a/media/jni/tuner/LnbClient.cpp +++ b/media/jni/tuner/LnbClient.cpp @@ -113,11 +113,14 @@ Result LnbClient::sendDiseqcMessage(vector<uint8_t> diseqcMessage) { Result LnbClient::close() { if (mTunerLnb != NULL) { Status s = mTunerLnb->close(); + mTunerLnb = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mLnb != NULL) { - return mLnb->close(); + Result res = mLnb->close(); + mLnb = NULL; + return res; } return Result::INVALID_STATE; diff --git a/media/jni/tuner/TimeFilterClient.cpp b/media/jni/tuner/TimeFilterClient.cpp index 432238d261e5..e123c9f57ce7 100644 --- a/media/jni/tuner/TimeFilterClient.cpp +++ b/media/jni/tuner/TimeFilterClient.cpp @@ -126,11 +126,14 @@ long TimeFilterClient::getSourceTime() { Result TimeFilterClient::close() { if (mTunerTimeFilter != NULL) { Status s = mTunerTimeFilter->close(); + mTunerTimeFilter = NULL; return ClientHelper::getServiceSpecificErrorCode(s); } if (mTimeFilter != NULL) { - return mTimeFilter->close(); + Result res = mTimeFilter->close(); + mTimeFilter = NULL; + return res; } return Result::INVALID_STATE; diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 314bf2910155..7a18bd5ae962 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -225,6 +225,7 @@ LIBANDROID { AStorageManager_unmountObb; ASurfaceControl_create; # introduced=29 ASurfaceControl_createFromWindow; # introduced=29 + ASurfaceControl_acquire; # introduced=31 ASurfaceControl_release; # introduced=29 ASurfaceTexture_acquireANativeWindow; # introduced=28 ASurfaceTexture_attachToGLContext; # introduced=28 diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 189be800e018..c1b5f1ddd423 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -185,10 +185,16 @@ ASurfaceControl* ASurfaceControl_create(ASurfaceControl* parent, const char* deb return reinterpret_cast<ASurfaceControl*>(surfaceControl.get()); } +void ASurfaceControl_acquire(ASurfaceControl* aSurfaceControl) { + SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + + SurfaceControl_acquire(surfaceControl); +} + void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) { - sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); - SurfaceControl_release(surfaceControl.get()); + SurfaceControl_release(surfaceControl); } ASurfaceTransaction* ASurfaceTransaction_create() { diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java index 9afa5d1311c5..92a792b78410 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -49,17 +49,6 @@ public final class ConnectivityFrameworkInitializer { } ); - // TODO: move outside of the connectivity JAR - SystemServiceRegistry.registerContextAwareService( - Context.VPN_MANAGEMENT_SERVICE, - VpnManager.class, - (context) -> { - final ConnectivityManager cm = context.getSystemService( - ConnectivityManager.class); - return cm.createVpnManager(); - } - ); - SystemServiceRegistry.registerContextAwareService( Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, ConnectivityDiagnosticsManager.class, diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index fbe15bbaa19c..92d7bf06aa9e 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -824,6 +824,7 @@ public class ConnectivityManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; + /** * A kludge to facilitate static access where a Context pointer isn't available, like in the * case of the static set/getProcessDefaultNetwork methods and from the Network class. @@ -1069,106 +1070,55 @@ public class ConnectivityManager { } /** - * Checks if a VPN app supports always-on mode. - * - * In order to support the always-on feature, an app has to - * <ul> - * <li>target {@link VERSION_CODES#N API 24} or above, and - * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} - * meta-data field. - * </ul> - * - * @param userId The identifier of the user for whom the VPN app is installed. - * @param vpnPackage The canonical package name of the VPN app. - * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ + @Deprecated public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - try { - return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); } /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - * <p>The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @param lockdownAllowlist The list of packages that are allowed to access network directly - * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so - * this method must be called when a package that should be allowed is installed or - * uninstalled. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. + * Calls VpnManager#setAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, lockdownAllowlist); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, + lockdownAllowlist); } - /** - * Returns the package name of the currently set always-on VPN application. - * If there is no always-on VPN set, or the VPN is provided by the system instead - * of by an app, {@code null} will be returned. - * - * @return Package name of VPN controller responsible for always-on VPN, - * or {@code null} if none is set. + /** + * Calls VpnManager#getAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public String getAlwaysOnVpnPackageForUser(int userId) { - try { - return mService.getAlwaysOnVpnPackage(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getAlwaysOnVpnPackageForUser(userId); } /** - * @return whether always-on VPN is in lockdown mode. - * + * Calls VpnManager#isVpnLockdownEnabled. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public boolean isVpnLockdownEnabled(int userId) { - try { - return mService.isVpnLockdownEnabled(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + return getVpnManager().isVpnLockdownEnabled(userId); } /** - * @return the list of packages that are allowed to access network when always-on VPN is in - * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. - * + * Calls VpnManager#getVpnLockdownAllowlist. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public List<String> getVpnLockdownWhitelist(int userId) { - try { - return mService.getVpnLockdownWhitelist(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + */ + @Deprecated + public List<String> getVpnLockdownAllowlist(int userId) { + return getVpnManager().getVpnLockdownAllowlist(userId); } /** @@ -1221,6 +1171,45 @@ public class ConnectivityManager { } /** + * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by + * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12 + * but is still supported for backwards compatibility. + * <p> + * This type of VPN is assumed always to use the system default network, and must always declare + * exactly one underlying network, which is the network that was the default when the VPN + * connected. + * <p> + * Calling this method with {@code true} enables legacy behaviour, specifically: + * <ul> + * <li>Any VPN that applies to userId 0 behaves specially with respect to deprecated + * {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the + * {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN + * connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network + * underlying the VPN.</li> + * <li>Deprecated APIs that return {@link NetworkInfo} objects will have their state + * similarly replaced by the VPN network state.</li> + * <li>Information on current network interfaces passed to NetworkStatsService will not + * include any VPN interfaces.</li> + * </ul> + * + * @param enabled whether legacy lockdown VPN is enabled or disabled + * + * TODO: @SystemApi(client = MODULE_LIBRARIES) + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void setLegacyLockdownVpnEnabled(boolean enabled) { + try { + mService.setLegacyLockdownVpnEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying * other apps. @@ -3180,20 +3169,13 @@ public class ConnectivityManager { } /** - * If the LockdownVpn mechanism is enabled, updates the vpn - * with a reload of its profile. - * - * @return a boolean with {@code} indicating success - * - * <p>This method can only be called by the system UID - * {@hide} + * Calls VpnManager#updateLockdownVpn. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide */ + @Deprecated public boolean updateLockdownVpn() { - try { - return mService.updateLockdownVpn(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().updateLockdownVpn(); } /** @@ -4557,6 +4539,8 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); + // TODO: Migrate callers to VpnManager#factoryReset. + getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4850,9 +4834,13 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** @hide */ - public VpnManager createVpnManager() { - return new VpnManager(mContext, mService); + /** + * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a + * private final mVpnManager because ConnectivityManager is initialized before VpnManager. + * @hide TODO: remove. + */ + public VpnManager getVpnManager() { + return mContext.getSystemService(VpnManager.class); } /** @hide */ @@ -4886,15 +4874,6 @@ public class ConnectivityManager { } } - private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { - try { - mService.setOemNetworkPreference(preference); - } catch (RemoteException e) { - Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); - throw e.rethrowFromSystemServer(); - } - } - @NonNull private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>(); @@ -5096,4 +5075,60 @@ public class ConnectivityManager { sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler)); } + + /** + * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor, + * OnSetOemNetworkPreferenceListener)}. + * @hide + */ + @SystemApi + public interface OnSetOemNetworkPreferenceListener { + /** + * Called when setOemNetworkPreference() successfully completes. + */ + void onComplete(); + } + + /** + * Used by automotive devices to set the network preferences used to direct traffic at an + * application level as per the given OemNetworkPreferences. An example use-case would be an + * automotive OEM wanting to provide connectivity for applications critical to the usage of a + * vehicle via a particular network. + * + * Calling this will overwrite the existing preference. + * + * @param preference {@link OemNetworkPreferences} The application network preference to be set. + * @param executor the executor on which listener will be invoked. + * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to + * communicate completion of setOemNetworkPreference(). This will only be + * called once upon successful completion of setOemNetworkPreference(). + * @throws IllegalArgumentException if {@code preference} contains invalid preference values. + * @throws SecurityException if missing the appropriate permissions. + * @throws UnsupportedOperationException if called on a non-automotive device. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) + public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final OnSetOemNetworkPreferenceListener listener) { + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + if (null != listener) { + Objects.requireNonNull(executor, "Executor must be non-null"); + } + final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null : + new IOnSetOemNetworkPreferenceListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::onComplete); + } + }; + + try { + mService.setOemNetworkPreference(preference, listenerInternal); + } catch (RemoteException e) { + Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); + throw e.rethrowFromSystemServer(); + } + } } diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index f909d1362550..6391802f3330 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; +import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; @@ -42,9 +43,6 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import com.android.connectivity.aidl.INetworkAgent; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; /** * Interface that answers queries about, and allows changing, the @@ -122,35 +120,8 @@ interface IConnectivityManager ProxyInfo getProxyForNetwork(in Network nework); - boolean prepareVpn(String oldPackage, String newPackage, int userId); - - void setVpnPackageAuthorization(String packageName, int userId, int vpnType); - - ParcelFileDescriptor establishVpn(in VpnConfig config); - - boolean provisionVpnProfile(in VpnProfile profile, String packageName); - - void deleteVpnProfile(String packageName); - - void startVpnProfile(String packageName); - - void stopVpnProfile(String packageName); - - VpnConfig getVpnConfig(int userId); - - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - void startLegacyVpn(in VpnProfile profile); - - LegacyVpnInfo getLegacyVpnInfo(int userId); - - boolean updateLockdownVpn(); - boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); - boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, - in List<String> lockdownWhitelist); - String getAlwaysOnVpnPackage(int userId); - boolean isVpnLockdownEnabled(int userId); - List<String> getVpnLockdownWhitelist(int userId); void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); + void setLegacyLockdownVpnEnabled(boolean enabled); void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); @@ -199,10 +170,6 @@ interface IConnectivityManager int getRestoreDefaultNetworkDelay(int networkType); - boolean addVpnAddress(String address, int prefixLength); - boolean removeVpnAddress(String address, int prefixLength); - boolean setUnderlyingNetworksForVpn(in Network[] networks); - void factoryReset(); void startNattKeepalive(in Network network, int intervalSeconds, @@ -222,8 +189,6 @@ interface IConnectivityManager byte[] getNetworkWatchlistConfigHash(); int getConnectionOwnerUid(in ConnectionInfo connectionInfo); - boolean isCallerCurrentAlwaysOnVpnApp(); - boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, in NetworkRequest request, String callingPackageName); @@ -245,5 +210,6 @@ interface IConnectivityManager void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); void unregisterQosCallback(in IQosCallback callback); - void setOemNetworkPreference(in OemNetworkPreferences preference); + void setOemNetworkPreference(in OemNetworkPreferences preference, + in IOnSetOemNetworkPreferenceListener listener); } diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 9d67f0b84367..26d14cbfaa95 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -2085,9 +2085,10 @@ public final class NetworkCapabilities implements Parcelable { /** * Check if private dns is broken. * - * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken. + * @return {@code true} if private DNS is broken on this network. * @hide */ + @SystemApi public boolean isPrivateDnsBroken() { return mPrivateDnsBroken; } @@ -2330,6 +2331,17 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Completely clears the contents of this object, removing even the capabilities that are + * set by default when the object is constructed. + * @return this builder + */ + @NonNull + public Builder clearAll() { + mCaps.clearAll(); + return this; + } + + /** * Sets the owner UID. * * The default value is {@link Process#INVALID_UID}. Pass this value to reset. diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index b4a651c0607e..4e3085f4704d 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -16,22 +16,6 @@ package android.net; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -46,8 +30,6 @@ import android.os.Process; import android.text.TextUtils; import android.util.proto.ProtoOutputStream; -import java.util.Arrays; -import java.util.List; import java.util.Objects; import java.util.Set; @@ -172,30 +154,8 @@ public class NetworkRequest implements Parcelable { * needed in terms of {@link NetworkCapabilities} features */ public static class Builder { - /** - * Capabilities that are currently compatible with VCN networks. - */ - private static final List<Integer> VCN_SUPPORTED_CAPABILITIES = Arrays.asList( - NET_CAPABILITY_CAPTIVE_PORTAL, - NET_CAPABILITY_DUN, - NET_CAPABILITY_FOREGROUND, - NET_CAPABILITY_INTERNET, - NET_CAPABILITY_NOT_CONGESTED, - NET_CAPABILITY_NOT_METERED, - NET_CAPABILITY_NOT_RESTRICTED, - NET_CAPABILITY_NOT_ROAMING, - NET_CAPABILITY_NOT_SUSPENDED, - NET_CAPABILITY_NOT_VPN, - NET_CAPABILITY_PARTIAL_CONNECTIVITY, - NET_CAPABILITY_TEMPORARILY_NOT_METERED, - NET_CAPABILITY_TRUSTED, - NET_CAPABILITY_VALIDATED); - private final NetworkCapabilities mNetworkCapabilities; - // A boolean that represents the user modified NOT_VCN_MANAGED capability. - private boolean mModifiedNotVcnManaged = false; - /** * Default constructor for Builder. */ @@ -217,7 +177,6 @@ public class NetworkRequest implements Parcelable { // maybeMarkCapabilitiesRestricted() doesn't add back. final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); nc.maybeMarkCapabilitiesRestricted(); - deduceNotVcnManagedCapability(nc); return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); } @@ -234,9 +193,6 @@ public class NetworkRequest implements Parcelable { */ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); - if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { - mModifiedNotVcnManaged = true; - } return this; } @@ -248,9 +204,6 @@ public class NetworkRequest implements Parcelable { */ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); - if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { - mModifiedNotVcnManaged = true; - } return this; } @@ -308,9 +261,6 @@ public class NetworkRequest implements Parcelable { @NonNull public Builder clearCapabilities() { mNetworkCapabilities.clearAll(); - // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities - // should not be add back later. - mModifiedNotVcnManaged = true; return this; } @@ -430,25 +380,6 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setSignalStrength(signalStrength); return this; } - - /** - * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities - * and user intention, which includes: - * 1. For the requests that don't have anything besides - * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to - * allow the callers automatically utilize VCN networks if available. - * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED, - * do not alter them to allow user fire request that suits their need. - * - * @hide - */ - private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) { - if (mModifiedNotVcnManaged) return; - for (final int cap : nc.getCapabilities()) { - if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return; - } - nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); - } } // implement the Parcelable interface diff --git a/core/java/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java index 082fa58f8ac2..0242ba08742c 100644 --- a/core/java/android/net/VpnTransportInfo.java +++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java @@ -16,8 +16,10 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; -import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.util.SparseArray; @@ -26,7 +28,15 @@ import com.android.internal.util.MessageUtils; import java.util.Objects; -/** @hide */ +/** + * Container for VPN-specific transport information. + * + * @see android.net.TransportInfo + * @see NetworkCapabilities#getTransportInfo() + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) public final class VpnTransportInfo implements TransportInfo, Parcelable { private static final SparseArray<String> sTypeToString = MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index 8fc318180778..ed1716fad8c0 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -25,7 +25,6 @@ cc_library_shared { ], srcs: [ "jni/com_android_server_TestNetworkService.cpp", - "jni/com_android_server_connectivity_Vpn.cpp", "jni/onload.cpp", ], shared_libs: [ diff --git a/packages/Connectivity/service/jni/onload.cpp b/packages/Connectivity/service/jni/onload.cpp index 3afcb0e8f688..00128794bcd0 100644 --- a/packages/Connectivity/service/jni/onload.cpp +++ b/packages/Connectivity/service/jni/onload.cpp @@ -19,7 +19,6 @@ namespace android { -int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_TestNetworkService(JNIEnv* env); extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { @@ -29,12 +28,11 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_ERR; } - if (register_android_server_connectivity_Vpn(env) < 0 - || register_android_server_TestNetworkService(env) < 0) { + if (register_android_server_TestNetworkService(env) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; } -};
\ No newline at end of file +}; diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS index 252670a6fb13..8e1774b0baa2 100644 --- a/packages/PackageInstaller/OWNERS +++ b/packages/PackageInstaller/OWNERS @@ -1,5 +1,4 @@ svetoslavganov@google.com -moltmann@google.com toddke@google.com suprabh@google.com diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml new file mode 100644 index 000000000000..46e2d4554d46 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml @@ -0,0 +1,35 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/> + <path + android:fillColor="#FF000000" + android:pathData="M13,8h2v3h-2z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.5,5h2v6h-2z" + android:fillAlpha="0.3"/> + <path + android:fillColor="#FF000000" + android:pathData="M20,3h2v8h-2z" + android:fillAlpha="0.3"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml new file mode 100644 index 000000000000..d9cd590e650a --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml @@ -0,0 +1,34 @@ +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/> + <path + android:fillColor="#FF000000" + android:pathData="M13,8h2v3h-2z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.5,5h2v6h-2z"/> + <path + android:fillColor="#FF000000" + android:pathData="M20,3h2v8h-2z" + android:fillAlpha="0.3"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml new file mode 100644 index 000000000000..e80fd08a8c0a --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml @@ -0,0 +1,33 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/> + <path + android:fillColor="#FF000000" + android:pathData="M13,8h2v3h-2z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.5,5h2v6h-2z"/> + <path + android:fillColor="#FF000000" + android:pathData="M20,3h2v8h-2z"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml new file mode 100644 index 000000000000..493912b0ddcd --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml @@ -0,0 +1,35 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/> + <path + android:fillColor="#FF000000" + android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z" + android:fillAlpha="0.3"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z" + android:fillAlpha="0.3"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml new file mode 100644 index 000000000000..af677fb604ae --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml @@ -0,0 +1,34 @@ +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/> + <path + android:fillColor="#FF000000" + android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z" + android:fillAlpha="0.3"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml new file mode 100644 index 000000000000..68b39da82ae5 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml @@ -0,0 +1,33 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/> + <path + android:fillColor="#FF000000" + android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/> + <path + android:fillColor="#FF000000" + android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"/> +</vector> diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 889980a05bfa..577fb6611448 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skep tans nuwe gebruiker …"</string> <string name="user_nickname" msgid="262624187455825083">"Bynaam"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Beëindig gastesessie"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gas"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Neem \'n foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Kies \'n prent"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data, drie stawe."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasein vol."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet is ontkoppel."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet gekoppel."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index c41e4d5d5869..c554e2ed8a8e 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"አዲስ ተጠቃሚ በመፍጠር ላይ…"</string> <string name="user_nickname" msgid="262624187455825083">"ቅጽል ስም"</string> <string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"የእንግዳ ክፍለ-ጊዜ ጨርስ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string> <string name="guest_nickname" msgid="6332276931583337261">"እንግዳ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ፎቶ አንሳ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ምስል ይምረጡ"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"የውሂብ ሦስት አሞሌዎች።"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"የውሂብ አመልካች ሙሉ ነው።"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ኤተርኔት ተነቅሏል።"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ኤተርኔት ተገናኝቷል።"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ኢተርኔት።"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index f8d1d576c759..b23cebd19e7a 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -558,7 +558,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"جارٍ إنشاء مستخدم جديد…"</string> <string name="user_nickname" msgid="262624187455825083">"اللقب"</string> <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"إنهاء جلسة الضيف"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string> <string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string> <string name="user_image_take_photo" msgid="467512954561638530">"التقاط صورة"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"اختيار صورة"</string> @@ -595,5 +595,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"إشارة البيانات تتكون من ثلاثة أشرطة."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"إشارة البيانات كاملة."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"تم قطع اتصال Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"تم إنشاء اتصال Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"إيثرنت"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index c6078f898c3e..2380b2fa21ac 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যৱহাৰকাৰী সৃষ্টি কৰি থকা হৈছে…"</string> <string name="user_nickname" msgid="262624187455825083">"উপনাম"</string> <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"অতিথিৰ ছেশ্বন সমাপ্ত কৰক"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string> <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string> <string name="user_image_take_photo" msgid="467512954561638530">"এখন ফট’ তোলক"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ডেটা ছিংগনেলত তিনিডাল দণ্ড আছে।"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ডেটা ছিগনেল পূৰা আছে।"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথাৰনেট সংযোগ বিচ্ছিন্ন হৈছে।"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথাৰনেট সংযোগ হৈছে।"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথাৰনেট।"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index d3e0a25593e4..f70bb84c55a4 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni istifadəçi yaradılır…"</string> <string name="user_nickname" msgid="262624187455825083">"Ləqəb"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Qonaq sessiyasını bitirin"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string> <string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Foto çəkin"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Şəkil seçin"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data üç xətdir."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data siqnalı tamdır."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kəsilib."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet qoşuludur."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 85412224e995..749f864f8aef 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -555,7 +555,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Pravi se novi korisnik…"</string> <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Slikaj"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string> @@ -592,5 +592,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal za podatke od tri crte."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za podatke je najjači."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa eternetom je prekinuta."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Eternet je povezan."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index aab600f3330e..6b2740d7d7d7 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ствараецца новы карыстальнік…"</string> <string name="user_nickname" msgid="262624187455825083">"Псеўданім"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Завяршыць гасцявы сеанс"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string> <string name="guest_nickname" msgid="6332276931583337261">"Госць"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Зрабіць фота"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбраць відарыс"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"3 планкі дадзеных."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Поўны сігнал перадачы дадзеных."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet адлучаны."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet падлучаны."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 92f293599f97..1a24b9e52b36 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Създава се нов потребител…"</string> <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Прекратяване на сесията като гост"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string> <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Правене на снимка"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Избиране на изображение"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Данните са с три чертички."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналът за данни е пълен."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Връзката с Ethernet е прекратена."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Установена е връзка с Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index b40e24e3a672..1c9f84f68962 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যবহারকারী তৈরি করা হচ্ছে…"</string> <string name="user_nickname" msgid="262624187455825083">"বিশেষ নাম"</string> <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"গেস্ট সেশন শেষ করুন"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string> <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ফটো তুলুন"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"একটি ইমেজ বেছে নিন"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"তিন দন্ড ডেটার সংকেত৷"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"পূর্ণ ডেটার সংকেত রয়েছে৷"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথারনেটের সংযোগ বিচ্ছিন্ন হয়েছে৷"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথারনেট সংযুক্ত হয়েছে৷"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথারনেট।"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index cec2e4533878..f70142187a36 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -555,7 +555,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kreiranje novog korisnika…"</string> <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Snimite fotografiju"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberite sliku"</string> @@ -592,5 +592,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Prijenos podataka na tri crtice."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za prijenos podataka pun."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa Ethernetom je prekinuta."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet je spojen."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 6134da873a0e..78ef10969b72 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"S\'està creant l\'usuari…"</string> <string name="user_nickname" msgid="262624187455825083">"Àlies"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Finalitza la sessió de convidat"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string> <string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fes una foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Tria una imatge"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Senyal de dades: tres barres."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Senyal de dades: complet."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"S\'ha desconnectat l\'Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connectada"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index f29a3dd54ff8..8ca9800562a0 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytváření nového uživatele…"</string> <string name="user_nickname" msgid="262624187455825083">"Přezdívka"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončení relace hosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string> <string name="guest_nickname" msgid="6332276931583337261">"Host"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Pořídit fotku"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrat obrázek"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tři čárky signálu datové sítě."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál datové sítě."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Síť ethernet je odpojena."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Síť ethernet je připojena."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index d92d41d536bc..5d55a12b4d6c 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string> <string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Afslut gæstesessionen"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Vælg et billede"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tre bjælker."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal fuldt."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er ikke tilsluttet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilsluttet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index ac05b620539c..1fbb391741b5 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Neuer Nutzer wird erstellt…"</string> <string name="user_nickname" msgid="262624187455825083">"Alias"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsitzung beenden"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Foto machen"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Bild auswählen"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datensignal - drei Balken"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Volle Datensignalstärke"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet nicht verbunden"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbunden"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 0b11d69132c5..32bcf8792067 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string> <string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Λήξη περιόδου σύνδεσης επισκέπτη"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string> <string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Λήψη φωτογραφίας"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Επιλογή εικόνας"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Τρεις γραμμές δεδομένων."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Πλήρες σήμα δεδομένων."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Το Ethernet αποσυνδέθηκε."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Το Ethernet συνδέθηκε."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index b98c4b8968e9..62419538309b 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> @@ -591,5 +591,6 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index aa0d3f1ddf18..198fee4b58af 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> @@ -591,5 +591,6 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index b98c4b8968e9..62419538309b 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> @@ -591,5 +591,6 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index b98c4b8968e9..62419538309b 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> @@ -591,5 +591,6 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index c01f3a05645e..27cd8b34889e 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"End guest session"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string> <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string> @@ -591,5 +591,6 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string> </resources> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 1da8e37186c6..0f765764dbfd 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string> <string name="user_nickname" msgid="262624187455825083">"Sobrenombre"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tomar una foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Elegir una imagen"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos completa"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index 54226ce0fe5c..abd209ccc717 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string> <string name="user_nickname" msgid="262624187455825083">"Apodo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Hacer foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Seleccionar una imagen"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos al máximo"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Conexión Ethernet desconectada."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conexión Ethernet conectada."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index 4c747e36ef52..2e9f3b8948a2 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Uue kasutaja loomine …"</string> <string name="user_nickname" msgid="262624187455825083">"Hüüdnimi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Lõpeta külastajaseanss"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string> <string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Pildistage"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Valige pilt"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Andmeside: kolm pulka."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Andmesignaal on tugev."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Etherneti-ühendus on katkestatud."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Etherneti-ühendus on loodud."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 6016cd20ff37..ecb4f7573b66 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Beste erabiltzaile bat sortzen…"</string> <string name="user_nickname" msgid="262624187455825083">"Goitizena"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Amaitu gonbidatuentzako saioa"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Atera argazki bat"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Aukeratu irudi bat"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datu-seinaleak hiru barra ditu."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datu-seinale osoa."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bidezko konexioa eten da."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bidez konektatu da."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index e85557000a61..56ade54d9212 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"درحال ایجاد کاربر جدید…"</string> <string name="user_nickname" msgid="262624187455825083">"نام مستعار"</string> <string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"پایان دادن به جلسه مهمان"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string> <string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string> <string name="user_image_take_photo" msgid="467512954561638530">"عکس گرفتن"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"انتخاب تصویر"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"سه نوار برای داده."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"قدرت سیگنال داده کامل است."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"اترنت قطع شد."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"اترنت متصل شد."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"اترنت."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 724e52a95927..e26ecb705c9f 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Luodaan uutta käyttäjää…"</string> <string name="user_nickname" msgid="262624187455825083">"Lempinimi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Lopeta Vierailija-käyttökerta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string> <string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ota kuva"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Valitse kuva"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datasignaali - kolme palkkia"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Vahva kuuluvuus."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet on irrotettu."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet on yhdistetty."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 74c3005b0228..d61ebc099934 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Créer un utilisateur…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Mettre fin à la session d\'invité"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Sélectionner une image"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index c9651ce570c5..64c06fae175d 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Création d\'un nouvel utilisateur…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Fermer la session Invité"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Choisir une image"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index f499f587a850..cf63dd7176b2 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario novo…"</string> <string name="user_nickname" msgid="262624187455825083">"Alcume"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escoller imaxe"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de sinal de datos"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de datos: completo"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Desconectouse a Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conectouse a Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 23ee1de46484..427ce56296fe 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"નવા વપરાશકર્તા બનાવી રહ્યાં છીએ…"</string> <string name="user_nickname" msgid="262624187455825083">"ઉપનામ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"અતિથિ સત્ર સમાપ્ત કરો"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string> <string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ફોટો લો"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"છબી પસંદ કરો"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ડેટા ત્રણ બાર."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ડેટા સિગ્નલ પૂર્ણ."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ઇથરનેટ ડિસ્કનેક્ટ થયું."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ઇથરનેટ કનેક્ટ થયું."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ઇથરનેટ."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index a2fc43c250cc..159d2dbe7439 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नया उपयोगकर्ता बनाया जा रहा है…"</string> <string name="user_nickname" msgid="262624187455825083">"प्रचलित नाम"</string> <string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करें"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string> <string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string> <string name="user_image_take_photo" msgid="467512954561638530">"फ़ोटो खींचें"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"कोई इमेज चुनें"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूरा."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ईथरनेट डिस्कनेक्ट किया गया."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ईथरनेट कनेक्ट किया गया."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ईथरनेट."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 396051d51a09..5e33ef3d0689 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -555,7 +555,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string> <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Završi gostujuću sesiju"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiraj"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string> @@ -592,5 +592,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatkovni signal tri stupca."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal pun."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Prekinuta je veza s ethernetom."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Uspostavljena je veza s ethernetom."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 0c9bc54c0861..28975be7b6fb 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Új felhasználó létrehozása…"</string> <string name="user_nickname" msgid="262624187455825083">"Becenév"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"A vendég munkamenet befejezése"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string> <string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotó készítése"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Kép kiválasztása"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Adat három sáv."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Adatjel teljes."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet leválasztva."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet csatlakoztatva."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 56c93b635e20..a2075265b58c 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ստեղծվում է օգտատիրոջ նոր պրոֆիլ…"</string> <string name="user_nickname" msgid="262624187455825083">"Կեղծանուն"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Ավարտել հյուրի աշխատաշրջանը"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string> <string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Լուսանկարել"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Ընտրել պատկեր"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Տվյալների երեք գիծ:"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Տվյալների ազդանշանը լրիվ է:"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet-ը անջատված է:"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet-ը կապակցված է:"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet։"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 0440f4796143..c4d752d25cf3 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Membuat pengguna baru …"</string> <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Akhiri sesi tamu"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string> <string name="guest_nickname" msgid="6332276931583337261">"Tamu"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih gambar"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga batang."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinyal data penuh."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet terputus."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tersambung."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 0bb294f5869b..56b0a81b7c81 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Stofnar nýjan notanda…"</string> <string name="user_nickname" msgid="262624187455825083">"Gælunafn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Ljúka gestalotu"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Taka mynd"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Velja mynd"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sendistyrkur gagnatengingar er þrjú strik."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Fullur sendistyrkur gagnatengingar."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet aftengt."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tengt."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 54049d79a76b..f045d273ce0d 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creazione nuovo utente…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Termina sessione Ospite"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string> <string name="guest_nickname" msgid="6332276931583337261">"Ospite"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Scatta una foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Scegli un\'immagine"</string> @@ -577,7 +577,7 @@ <string name="data_connection_4g_plus" msgid="5194902328408751020">"4G+"</string> <string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string> <string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string> - <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"Wi-Fi operatore"</string> + <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"CWF"</string> <string name="cell_data_off_content_description" msgid="2280700839891636498">"Dati mobili disattivati"</string> <string name="not_default_data_content_description" msgid="6517068332106592887">"Non impostato per l\'utilizzo dei dati"</string> <string name="accessibility_no_phone" msgid="2687419663127582503">"Nessun telefono."</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: tre barre."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Massimo segnale dati."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Connessione Ethernet annullata."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Connessione Ethernet stabilita."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 6cc4347946f4..ba3a46786663 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"בתהליך יצירה של משתמש חדש…"</string> <string name="user_nickname" msgid="262624187455825083">"כינוי"</string> <string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"הפסקת הגלישה כאורח"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string> <string name="guest_nickname" msgid="6332276931583337261">"אורח"</string> <string name="user_image_take_photo" msgid="467512954561638530">"צילום תמונה"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"לבחירת תמונה"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"שלושה פסים של נתונים."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"אות הנתונים מלא."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"אתרנט מנותק."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"אתרנט מחובר."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"אתרנט."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index ec674adb75d0..6f398b6840b0 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"新しいユーザーを作成しています…"</string> <string name="user_nickname" msgid="262624187455825083">"ニックネーム"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"ゲスト セッションを終了する"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string> <string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string> <string name="user_image_take_photo" msgid="467512954561638530">"写真を撮る"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"画像を選択"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"データ信号:レベル3"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"データ信号:フル"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"イーサネット接続を解除しました。"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"イーサネットに接続しました。"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"イーサネット。"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 30ab3b49afdd..5bf1dd03f558 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string> <string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string> <string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"სტუმრის სესიის დასრულება"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string> <string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"მონაცემების გადაცემა: სამი ზოლი"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"მონაცემთა გადაცემის საიმედო სიგნალი."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet კავშირი შეწყვეტილია."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet დაკავშირებულია."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index ef7852710af4..c7c38378db6e 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string> <string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Қонақ сеансын аяқтау"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string> <string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дерекқор үш баған."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дерекқор сигналы толы."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажыратылған."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet қосылған."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index ed59c747f8fd..99a73e14daee 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើតអ្នកប្រើប្រាស់ថ្មី…"</string> <string name="user_nickname" msgid="262624187455825083">"ឈ្មោះហៅក្រៅ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូលភ្ញៀវ"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"បញ្ចប់វគ្គភ្ញៀវ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"លុបភ្ញៀវ"</string> <string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើសរូបភាព"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ទិន្នន័យបីកាំ។"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"សញ្ញាទិន្នន័យពេញ។"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"បានផ្តាច់អ៊ីសឺរណិត។"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"បានភ្ជាប់អ៊ីសឺរណិត។"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"អ៊ីសឺរណិត។"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 995dc8632e5c..54a42a8b0090 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="user_nickname" msgid="262624187455825083">"ಅಡ್ಡ ಹೆಸರು"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"ಅತಿಥಿ ಸೆಷನ್ ಅಂತ್ಯಗೊಳಿಸಿ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string> <string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಿ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ಚಿತ್ರವನ್ನು ಆರಿಸಿ"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ಡೇಟಾ ಮೂರು ಪಟ್ಟಿಗಳು."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ಡೇಟಾ ಸಂಕೇತ ತುಂಬಿದೆ."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕಗೊಳಿಸಲಾಗಿದೆ."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ಇಥರ್ನೆಟ್."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 7863d481295e..9e9fea9e57ad 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"새로운 사용자를 만드는 중…"</string> <string name="user_nickname" msgid="262624187455825083">"닉네임"</string> <string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"게스트 세션 종료"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string> <string name="guest_nickname" msgid="6332276931583337261">"게스트"</string> <string name="user_image_take_photo" msgid="467512954561638530">"사진 찍기"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"이미지 선택"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"데이터 신호 막대가 세 개입니다."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"데이터 신호가 강합니다."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"이더넷에서 연결 해제되었습니다."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"이더넷에 연결되었습니다."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"이더넷에 연결되었습니다."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml index 6af32ccbad1d..7c0fbaee2768 100644 --- a/packages/SettingsLib/res/values-ky/arrays.xml +++ b/packages/SettingsLib/res/values-ky/arrays.xml @@ -86,7 +86,7 @@ <item msgid="8147982633566548515">"карта14"</item> </string-array> <string-array name="bluetooth_a2dp_codec_titles"> - <item msgid="2494959071796102843">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item> <item msgid="4055460186095649420">"SBC"</item> <item msgid="720249083677397051">"AAC"</item> <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item> @@ -94,7 +94,7 @@ <item msgid="3825367753087348007">"LDAC"</item> </string-array> <string-array name="bluetooth_a2dp_codec_summaries"> - <item msgid="8868109554557331312">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item> <item msgid="9024885861221697796">"SBC"</item> <item msgid="4688890470703790013">"AAC"</item> <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item> @@ -102,38 +102,38 @@ <item msgid="2553206901068987657">"LDAC"</item> </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> - <item msgid="926809261293414607">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item> <item msgid="8003118270854840095">"44,1 кГц"</item> <item msgid="3208896645474529394">"48,0 кГц"</item> <item msgid="8420261949134022577">"88,2 кГц"</item> <item msgid="8887519571067543785">"96,0 кГц"</item> </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_summaries"> - <item msgid="2284090879080331090">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="2284090879080331090">"Система тандаганды колдонуу (демейки)"</item> <item msgid="1872276250541651186">"44,1 кГц"</item> <item msgid="8736780630001704004">"48,0 кГц"</item> <item msgid="7698585706868856888">"88,2 кГц"</item> <item msgid="8946330945963372966">"96,0 кГц"</item> </string-array> <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles"> - <item msgid="2574107108483219051">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="2574107108483219051">"Система тандаганды колдонуу (демейки)"</item> <item msgid="4671992321419011165">"16 бит/үлгү"</item> <item msgid="1933898806184763940">"24 бит/үлгү"</item> <item msgid="1212577207279552119">"32 бит/үлгү"</item> </string-array> <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries"> - <item msgid="9196208128729063711">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="9196208128729063711">"Система тандаганды колдонуу (демейки)"</item> <item msgid="1084497364516370912">"16 бит/үлгү"</item> <item msgid="2077889391457961734">"24 бит/үлгү"</item> <item msgid="3836844909491316925">"32 бит/үлгү"</item> </string-array> <string-array name="bluetooth_a2dp_codec_channel_mode_titles"> - <item msgid="3014194562841654656">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="3014194562841654656">"Система тандаганды колдонуу (демейки)"</item> <item msgid="5982952342181788248">"Моно"</item> <item msgid="927546067692441494">"Стерео"</item> </string-array> <string-array name="bluetooth_a2dp_codec_channel_mode_summaries"> - <item msgid="1997302811102880485">"Тутум тандаганды колдонуу (демейки)"</item> + <item msgid="1997302811102880485">"Система тандаганды колдонуу (демейки)"</item> <item msgid="8005696114958453588">"Моно"</item> <item msgid="1333279807604675720">"Стерео"</item> </string-array> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 45d05c6372ce..2aec5cb42199 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -143,7 +143,7 @@ <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Алынып салынган колдонмолор"</string> <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Өчүрүлгөн колдонмолор жана колдонуучулар"</string> - <string name="data_usage_ota" msgid="7984667793701597001">"Тутум жаңыртуулары"</string> + <string name="data_usage_ota" msgid="7984667793701597001">"Системанын жаңыртуулары"</string> <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB модем"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Wi-Fi байланыш түйүнү"</string> <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth модем"</string> @@ -162,7 +162,7 @@ <string name="tts_default_pitch_title" msgid="6988592215554485479">"Негизги тон"</string> <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Синтезделген кептин интонациясына таасирин тийгизет"</string> <string name="tts_default_lang_title" msgid="4698933575028098940">"Тил"</string> - <string name="tts_lang_use_system" msgid="6312945299804012406">"Тутум тилин колдонуу"</string> + <string name="tts_lang_use_system" msgid="6312945299804012406">"Системанын тилин колдонуу"</string> <string name="tts_lang_not_selected" msgid="7927823081096056147">"Тил тандалган жок"</string> <string name="tts_default_lang_summary" msgid="9042620014800063470">"Текстти окуй турган тилди тандоо"</string> <string name="tts_play_example_title" msgid="1599468547216481684">"Үлгүнү угуу"</string> @@ -483,7 +483,7 @@ <string name="retail_demo_reset_next" msgid="3688129033843885362">"Кийинки"</string> <string name="retail_demo_reset_title" msgid="1866911701095959800">"Сырсөз талап кылынат"</string> <string name="active_input_method_subtypes" msgid="4232680535471633046">"Жигердүү киргизүү ыкмалары"</string> - <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Тутум тилдерин колдонуу"</string> + <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Системанын тилдерин колдонуу"</string> <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> тууралоолору ачылган жок"</string> <string name="ime_security_warning" msgid="6547562217880551450">"Бул киргизүү ыкмасы сиз терген бардык тексттер, сырсөздөр жана кредиттик карталар сыяктуу жеке маалыматтарды кошо чогултушу мүмкүн. Бул <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> колдонмосу менен байланыштуу. Ушул киргизүү ыкма колдонулсунбу?"</string> <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Эскертүү: Өчүрүп-күйгүзгөндөн кийин, бул колдонмо телефондун кулпусу ачылмайынча иштебейт"</string> @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңы колдонуучу түзүлүүдө…"</string> <string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Конок сеансын бүтүрүү"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string> <string name="guest_nickname" msgid="6332276931583337261">"Конок"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Сүрөт тандаңыз"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Мобилдик интернеттин сигналы үч таякча."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Мобилдик интернеттин сигналы толук."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажырады."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet туташты."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index ca6e06c13892..e199e1f7db83 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ກຳລັງສ້າງຜູ້ໃຊ້ໃໝ່…"</string> <string name="user_nickname" msgid="262624187455825083">"ຊື່ຫຼິ້ນ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"ສິ້ນສຸດເຊດຊັນແຂກ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string> <string name="guest_nickname" msgid="6332276931583337261">"ແຂກ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ຖ່າຍຮູບ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ເລືອກຮູບ"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ຂໍ້ມູນສາມຂີດ."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ສັນຍານຂໍ້ມູນເຕັມ."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ອີເທີເນັດຕັດເຊື່ອມຕໍ່ແລ້ວ."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ອີເທີເນັດເຊື່ອມຕໍ່ແລ້ວ."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ອີເທີເນັດ."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 173b57eaba57..a25c59f5eb26 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kuriamas naujas naudotojas…"</string> <string name="user_nickname" msgid="262624187455825083">"Slapyvardis"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Baigti svečio sesiją"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string> <string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotografuoti"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pasirinkti vaizdą"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Trys duomenų juostos."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Stiprus duomenų signalas."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Atsijungta nuo eterneto."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Prijungta prie eterneto."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternetas."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index a8bd2cc34a51..f360c906d95a 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -555,7 +555,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Notiek jauna lietotāja izveide…"</string> <string name="user_nickname" msgid="262624187455825083">"Segvārds"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Beigt viesa sesiju"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string> <string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Uzņemt fotoattēlu"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Izvēlēties attēlu"</string> @@ -592,5 +592,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: trīs joslas."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Pilna piekļuve datu signālam."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Pārtraukts savienojums ar tīklu Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Izveidots savienojums ar tīklu Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Tīkls Ethernet"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 0ef3f0eee6b5..a32d00b00928 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Се создава нов корисник…"</string> <string name="user_nickname" msgid="262624187455825083">"Прекар"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши ја гостинската сесија"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string> <string name="guest_nickname" msgid="6332276931583337261">"Гостин"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Фотографирајте"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Одберете слика"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Податоци три цртички."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналот за податоци е исполнет."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Етернетот е исклучен."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернетот е поврзан."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index e6289ae3591e..902507df89c8 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"പുതിയ ഉപയോക്താവിനെ സൃഷ്ടിക്കുന്നു…"</string> <string name="user_nickname" msgid="262624187455825083">"വിളിപ്പേര്"</string> <string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"അതിഥി സെഷൻ അവസാനിപ്പിക്കുക"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string> <string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ഒരു ഫോട്ടോ എടുക്കുക"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ഡാറ്റ മൂന്ന് ബാർ."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ഡാറ്റ സിഗ്നൽ പൂർണ്ണമാണ്."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ഇതർനെറ്റ് വിച്ഛേദിച്ചു."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ഇതർനെറ്റ് കണക്റ്റുചെയ്തു."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ഇതർനെറ്റ്."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index ead712c30b89..83009b7c03a1 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Шинэ хэрэглэгч үүсгэж байна…"</string> <string name="user_nickname" msgid="262624187455825083">"Хоч"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Зочны сургалтыг дуусгах"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string> <string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Зураг авах"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Зураг сонгох"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дата гурван баганатай."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дата дохио дүүрэн."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet саллаа."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet холбогдсон."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Этернэт."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index edcc71b17e8a..359f52565934 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नवीन वापरकर्ता तयार करत आहे…"</string> <string name="user_nickname" msgid="262624187455825083">"टोपणनाव"</string> <string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथी सत्र संपवा"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string> <string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string> <string name="user_image_take_photo" msgid="467512954561638530">"फोटो काढा"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"इमेज निवडा"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूर्ण."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट डिस्कनेक्ट केले."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट कनेक्ट केले."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 8a14437e3647..eba89daf02ed 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Mencipta pengguna baharu…"</string> <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Tamatkan sesi tetamu"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string> <string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih imej"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga bar."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Isyarat data penuh."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet diputuskan sambungan."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet disambungkan."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 377c8b241144..e982aa93d28e 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"အသုံးပြုသူအသစ် ပြုလုပ်နေသည်…"</string> <string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"ဧည့်သည်ဆက်ရှင်ကို အဆုံးသတ်ရန်"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string> <string name="guest_nickname" msgid="6332276931583337261">"ဧည့်သည်"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ဓာတ်ပုံရိုက်ရန်"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ပုံရွေးရန်"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ဒေတာသုံးဘား။"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ဒေတာထုတ်လွှင့်မှုအပြည့်ဖမ်းမိခြင်း"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet နှင့်ချိတ်ဆက်မှုပြတ်တောက်"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ချိတ်ဆက်ထား။"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"အီသာနက်။"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index f0e773b1833d..abbb5e7c64ee 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Oppretter en ny bruker …"</string> <string name="user_nickname" msgid="262624187455825083">"Kallenavn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Avslutt gjesteøkten"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ta et bilde"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Velg et bilde"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data – tre stolper."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal er fullt."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er frakoblet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilkoblet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 0bde70fe209d..d7a32bf3a038 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नयाँ प्रयोगकर्ता बनाउँदै…"</string> <string name="user_nickname" msgid="262624187455825083">"उपनाम"</string> <string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथिको सत्र अन्त्य गर्नुहोस्"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string> <string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string> <string name="user_image_take_photo" msgid="467512954561638530">"फोटो खिच्नुहोस्"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"कुनै फोटो छनौट गर्नुहोस्"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तिन बाधाहरू।"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा संकेत पूर्ण।"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट विच्छेद भयो।"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट जोडियो।"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट।"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 2bc1e8c0f7f0..7e739bca9b94 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Nieuwe gebruiker maken…"</string> <string name="user_nickname" msgid="262624187455825083">"Bijnaam"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsessie beëindigen"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Foto maken"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Afbeelding kiezen"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Gegevens: drie streepjes."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Gegevenssignaal is op volle sterkte."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetverbinding verbroken."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbonden."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 787c87182cd7..1e842b7f7ce0 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଉଛି…"</string> <string name="user_nickname" msgid="262624187455825083">"ଡାକନାମ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"ଅତିଥି ସେସନ୍ ଶେଷ କରନ୍ତୁ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string> <string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ଏକ ଛବି ବାଛନ୍ତୁ"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ଡାଟାର ତିନୋଟି ବାର୍ ଅଛି।"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ଡାଟା ସିଗ୍ନାଲ୍ ପୂର୍ଣ୍ଣ ଅଛି।"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ଇଥରନେଟ୍ ବିଚ୍ଛିନ୍ନ ହୋଇଛି।"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ଇଥରନେଟ୍ ସଂଯୁକ୍ତ ହୋଇଛି।"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ଇଥରନେଟ୍।"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 9483e060081c..d035fc0b5c9c 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…"</string> <string name="user_nickname" msgid="262624187455825083">"ਉਪਨਾਮ"</string> <string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਸਮਾਪਤ ਕਰੋ"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string> <string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ਇੱਕ ਫ਼ੋਟੋ ਖਿੱਚੋ"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ਕੋਈ ਚਿੱਤਰ ਚੁਣੋ"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">" ਡਾਟਾ ਤਿੰਨ ਬਾਰ।"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">" ਡਾਟਾ ਸਿਗਨਲ ਪੂਰਾ।"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ਈਥਰਨੈੱਟ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ।"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ਈਥਰਨੈੱਟ ਕਨੈਕਟ ਹੋ ਗਿਆ।"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ਈਥਰਨੈੱਟ।"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index c7f1595c86d0..8a732cb7710b 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Tworzę nowego użytkownika…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Kończenie sesji gościa"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gość"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Zrób zdjęcie"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Wybierz obraz"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dane: trzy paski."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Dane: pełna moc sygnału."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Rozłączono z siecią Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Połączono z siecią Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 881bccb90062..a63b9b5660da 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string> <string name="user_nickname" msgid="262624187455825083">"Apelido"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 94cad918cab8..ab23059e013c 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"A criar novo utilizador…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudónimo"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Terminar a sessão de convidado"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras de dados."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados completo."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desligada."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ligada."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 881bccb90062..a63b9b5660da 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string> <string name="user_nickname" msgid="262624187455825083">"Apelido"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string> <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 63d8b8f53f7f..98edb32e2865 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -555,7 +555,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Se creează un utilizator nou…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Încheiați sesiunea pentru invitați"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string> <string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Faceți o fotografie"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Alegeți o imagine"</string> @@ -592,5 +592,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Semnal pentru date: trei bare."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Semnal pentru date: complet."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet deconectat."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectat."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index ac43e493d73b..972dc205fea5 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string> <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Завершить гостевой сеанс"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string> <string name="guest_nickname" msgid="6332276931583337261">"Гость"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбрать фото"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал передачи данных: три деления."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Надежный сигнал передачи данных."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Устройство отключено от Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Устройство подключено к Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index 08fd9a03d7c4..0dee8e1c8af9 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"නව පරිශීලක තනමින්…"</string> <string name="user_nickname" msgid="262624187455825083">"අපනාමය"</string> <string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"ආරාධිත සැසිය අවසන් කරන්න"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string> <string name="guest_nickname" msgid="6332276931583337261">"අමුත්තා"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ඡායාරූපයක් ගන්න"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"රූපයක් තෝරන්න"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"දත්ත තීරු 3."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"දත්ත සංඥාව පිරී ඇත."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ඊතර්නෙට් විසන්ධි කරන ලදී."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ඊතර්නෙට් සම්බන්ධ කරන ලදී."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ඊතර්නෙට්."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 8f05684e6de9..7d49ca3b91cd 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytvára sa nový používateľ…"</string> <string name="user_nickname" msgid="262624187455825083">"Prezývka"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončiť reláciu hosťa"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string> <string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Odfotiť"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrať obrázok"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tri čiarky signálu dátovej siete."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál dátovej siete."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Sieť ethernet je odpojená"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Sieť ethernet je pripojená"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 3dccf3b53f9c..48e5dcc2284b 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ustvarjanje novega uporabnika …"</string> <string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Končaj sejo gosta"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiranje"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Izberi sliko"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatki s tremi črticami."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal poln."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetna povezava je prekinjena."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernetna povezava je vzpostavljena."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index c09ad4c42fa4..0bc905e688a1 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Po krijohet një përdorues i ri…"</string> <string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Jepi fund sesionit të vizitorit"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string> <string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Bëj një fotografi"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Zgjidh një imazh"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sinjali është me tre vija."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinjali i të dhënave është i plotë."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Lidhja e eternetit u shkëput."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Lidhja e eternetit u lidh."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index b5a91bfed1d0..6d19af89f47b 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -555,7 +555,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Прави се нови корисник…"</string> <string name="user_nickname" msgid="262624187455825083">"Надимак"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши сесију госта"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string> <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Сликај"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Одабери слику"</string> @@ -592,5 +592,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал за податке од три црте."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигнал за податке је најјачи."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Веза са етернетом је прекинута."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернет је повезан."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 16a1be606707..944c423b35ac 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skapar ny användare …"</string> <string name="user_nickname" msgid="262624187455825083">"Smeknamn"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Avsluta gästsession"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string> <string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Ta ett foto"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Välj en bild"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data: tre staplar."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignalen är full."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet har kopplats från."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet har anslutits."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 58896c87baaa..442f54be37bb 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string> <string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Maliza kipindi cha mgeni"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string> <string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Chagua picha"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Fito tatu za habari."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Ishara ya data imejaa."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethaneti imeondolewa."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethaneti imeunganishwa."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethaneti."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 71cf7d41fcc4..561a122430e7 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string> <string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string> <string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"விருந்தினர் அமர்வை நிறைவுசெய்"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string> <string name="guest_nickname" msgid="6332276931583337261">"கெஸ்ட்"</string> <string name="user_image_take_photo" msgid="467512954561638530">"படமெடுங்கள்"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"படத்தைத் தேர்வுசெய்யுங்கள்"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"தரவு சிக்னல் மூன்று கோட்டில் உள்ளது."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"தரவு சிக்னல் முழுமையாக உள்ளது."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ஈத்தர்நெட் துண்டிக்கப்பட்டது."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ஈத்தர்நெட் இணைக்கப்பட்டது."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ஈதர்நெட்."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index c3a65e702a28..18d7f281db62 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string> <string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string> <string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"గెస్ట్ సెషన్ను ముగించు"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string> <string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ఇమేజ్ను ఎంచుకోండి"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"డేటా మూడు బార్లు."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"డేటా సిగ్నల్ సంపూర్ణంగా ఉంది."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ఈథర్నెట్ డిస్కనెక్ట్ చేయబడింది."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ఈథర్నెట్ కనెక్ట్ చేయబడింది."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ఈథర్నెట్."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 903accbdc7ff..ceab26cabd9a 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string> <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string> <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"จบเซสชันผู้เยี่ยมชม"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string> <string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"สัญญาณข้อมูลสามขีด"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"สัญญาณข้อมูลเต็ม"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ยกเลิกการเชื่อมต่ออีเทอร์เน็ตแล้ว"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"เชื่อมต่ออีเทอร์เน็ตแล้ว"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"อีเทอร์เน็ต"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index b259201fe029..671fa149219f 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Gumagawa ng bagong user…"</string> <string name="user_nickname" msgid="262624187455825083">"Nickname"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Tapusin ang session ng bisita"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string> <string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Kumuha ng larawan"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Pumili ng larawan"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data na tatlong bar."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Puno ang signal ng data."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Nadiskonekta ang Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Nakakonekta ang Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index a0bc3626d086..98d89f3e43d5 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni kullanıcı oluşturuluyor…"</string> <string name="user_nickname" msgid="262624187455825083">"Takma ad"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Misafir oturumunu sonlandır"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string> <string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Fotoğraf çek"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Resim seç"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Veri sinyali üç çubuk."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Veri sinyali tam."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kesildi."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bağlandı."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index 509c8a602794..4e739acb53a7 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -556,7 +556,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Створення нового користувача…"</string> <string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Завершити сеанс у режимі \"Гість\""</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string> <string name="guest_nickname" msgid="6332276931583337261">"Гість"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Зробити фотографію"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Вибрати зображення"</string> @@ -593,5 +593,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Три смужки сигналу даних."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Максимальний сигнал даних."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet відключено."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet підключено."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index e097ab2f5789..c7dee9513759 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"نیا صارف تخلیق کرنا…"</string> <string name="user_nickname" msgid="262624187455825083">"عرفی نام"</string> <string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"مہمان سیشن ختم کریں"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string> <string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ایک تصویر لیں"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"ایک تصویر منتخب کریں"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ڈیٹا کے تین بارز۔"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ڈیٹا سگنل بھرا ہوا ہے۔"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ایتھرنیٹ منقطع ہے۔"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ایتھرنیٹ منسلک ہے۔"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ایتھرنیٹ۔"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 8c2a95d5c316..433096b64f08 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string> <string name="user_nickname" msgid="262624187455825083">"Nik"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Mehmon seansini yakunlash"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string> <string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Ma’lumotlar uchta panelda."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Internet signali butun."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Qurilma Ethernet tarmog‘idan uzildi."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Qurilma Ethernet tarmog‘iga ulandi."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 7a4d89bd4f22..a14462bf7efe 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Đang tạo người dùng mới…"</string> <string name="user_nickname" msgid="262624187455825083">"Biệt hiệu"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Kết thúc phiên khách"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string> <string name="guest_nickname" msgid="6332276931583337261">"Khách"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Chụp ảnh"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Chọn một hình ảnh"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tín hiệu dữ liệu ba vạch."</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Tín hiệu dữ liệu đầy đủ."</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Đã ngắt kết nối Ethernet."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Đã kết nối Ethernet."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 7cd61fc5491c..f6bea916c3fa 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在创建新用户…"</string> <string name="user_nickname" msgid="262624187455825083">"昵称"</string> <string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"结束访客会话"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string> <string name="guest_nickname" msgid="6332276931583337261">"访客"</string> <string name="user_image_take_photo" msgid="467512954561638530">"拍摄照片"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"选择图片"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"数据信号强度为三格。"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"数据信号满格。"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太网已断开连接。"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"以太网已连接。"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太网。"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 458071cd8cb2..3cf15a9f3995 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string> <string name="user_nickname" msgid="262624187455825083">"暱稱"</string> <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string> <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string> <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網絡訊號強度為三格。"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網絡訊號滿格。"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太網連接中斷。"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連接以太網。"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太網絡。"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 867ed8b61c17..7cf02978efd8 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string> <string name="user_nickname" msgid="262624187455825083">"暱稱"</string> <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string> <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string> <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網路訊號強度三格。"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網路訊號滿格。"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"未連上乙太網路。"</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連上乙太網路。"</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"乙太網路。"</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index e64dbd30dc82..d950d58b5bf7 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -554,7 +554,7 @@ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Idala umsebenzisi omusha…"</string> <string name="user_nickname" msgid="262624187455825083">"Isiteketiso"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string> - <string name="guest_exit_guest" msgid="4754204715192830850">"Misa isikhathi sesihambeli"</string> + <string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string> <string name="guest_nickname" msgid="6332276931583337261">"Isihambeli"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Thatha isithombe"</string> <string name="user_image_choose_photo" msgid="1363820919146782908">"Khetha isithombe"</string> @@ -591,5 +591,7 @@ <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Amabha amathathu edatha"</string> <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Igcwele i-signal yedatha"</string> <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"I-Ethernet inqanyuliwe."</string> - <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"I-Ethernet ixhunyiwe."</string> + <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"I-Ethernet."</string> + <!-- no translation found for accessibility_no_calling (3540827068323895748) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 7556ace466f2..3866151982a4 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1380,7 +1380,7 @@ <!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] --> <string name="guest_new_guest">Add guest</string> <!-- Label for exiting and removing the guest session in the user switcher [CHAR LIMIT=35] --> - <string name="guest_exit_guest">End guest session</string> + <string name="guest_exit_guest">Remove guest</string> <!-- Name for the guest user [CHAR LIMIT=35] --> <string name="guest_nickname">Guest</string> @@ -1485,4 +1485,7 @@ <string name="accessibility_ethernet_disconnected">Ethernet disconnected.</string> <!-- Content description of the Ethernet connection when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_ethernet_connected">Ethernet.</string> + + <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_no_calling">No calling.</string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java index 45028ff8f4f9..eff9e74e0e70 100644 --- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java +++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java @@ -48,6 +48,8 @@ public class AccessibilityContentDescriptions { public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi; + public static final int NO_CALLING = R.string.accessibility_no_calling; + public static final int[] ETHERNET_CONNECTION_VALUES = { R.string.accessibility_ethernet_disconnected, R.string.accessibility_ethernet_connected, diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java index 4c7b898a4fb5..0cd5e4ded168 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java @@ -266,7 +266,7 @@ public class MobileStatusTracker { serviceState.getDataRegState()) + ")") .append(',') .append("signalStrength=").append(signalStrength == null ? "" - : signalStrength.toString()).append(',') + : signalStrength.getLevel()).append(',') .append("telephonyDisplayInfo=").append(telephonyDisplayInfo == null ? "" : telephonyDisplayInfo.toString()).append(']').toString(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java index 0cb9906b9a82..e3413aac08ad 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java @@ -317,5 +317,21 @@ public class TelephonyIcons { ICON_NAME_TO_ICON.put("datadisable", DATA_DISABLED); ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA); } + + public static final int[] WIFI_CALL_STRENGTH_ICONS = { + R.drawable.ic_wifi_call_strength_1, + R.drawable.ic_wifi_call_strength_1, + R.drawable.ic_wifi_call_strength_2, + R.drawable.ic_wifi_call_strength_3, + R.drawable.ic_wifi_call_strength_3 + }; + + public static final int[] MOBILE_CALL_STRENGTH_ICONS = { + R.drawable.ic_mobile_call_strength_1, + R.drawable.ic_mobile_call_strength_1, + R.drawable.ic_mobile_call_strength_2, + R.drawable.ic_mobile_call_strength_3, + R.drawable.ic_mobile_call_strength_3 + }; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index 78282fb14f5f..841a49e6d4fd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -39,6 +39,8 @@ import android.util.FeatureFlagUtils; import com.android.settingslib.R; import com.android.settingslib.Utils; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -47,6 +49,8 @@ import java.util.Set; * Track status of Wi-Fi for the Sys UI. */ public class WifiStatusTracker { + private static final int HISTORY_SIZE = 32; + private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); private final Context mContext; private final WifiNetworkScoreCache mWifiNetworkScoreCache; private final WifiManager mWifiManager; @@ -54,6 +58,10 @@ public class WifiStatusTracker { private final ConnectivityManager mConnectivityManager; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Set<Integer> mNetworks = new HashSet<>(); + // Save the previous HISTORY_SIZE states for logging. + private final String[] mHistory = new String[HISTORY_SIZE]; + // Where to copy the next state into. + private int mHistoryIndex; private final WifiNetworkScoreCache.CacheListener mCacheListener = new WifiNetworkScoreCache.CacheListener(mHandler) { @Override @@ -93,6 +101,13 @@ public class WifiStatusTracker { } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo(); } + String log = new StringBuilder() + .append(SSDF.format(System.currentTimeMillis())).append(",") + .append("onCapabilitiesChanged: ") + .append("network=").append(network).append(",") + .append("networkCapabilities=").append(networkCapabilities) + .toString(); + recordLastWifiNetwork(log); if (wifiInfo != null) { updateWifiInfo(wifiInfo); updateStatusLabel(); @@ -102,6 +117,12 @@ public class WifiStatusTracker { @Override public void onLost(Network network) { + String log = new StringBuilder() + .append(SSDF.format(System.currentTimeMillis())).append(",") + .append("onLost: ") + .append("network=").append(network) + .toString(); + recordLastWifiNetwork(log); if (mNetworks.contains(network.getNetId())) { mNetworks.remove(network.getNetId()); updateWifiInfo(null); @@ -336,4 +357,25 @@ public class WifiStatusTracker { } return null; } + + private void recordLastWifiNetwork(String log) { + mHistory[mHistoryIndex] = log; + mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; + } + + /** Dump function. */ + public void dump(PrintWriter pw) { + pw.println(" - WiFi Network History ------"); + int size = 0; + for (int i = 0; i < HISTORY_SIZE; i++) { + if (mHistory[i] != null) size++; + } + // Print out the previous states in ordered number. + for (int i = mHistoryIndex + HISTORY_SIZE - 1; + i >= mHistoryIndex + HISTORY_SIZE - size; i--) { + pw.println(" Previous WiFiNetwork(" + + (mHistoryIndex + HISTORY_SIZE - i) + "): " + + mHistory[i & (HISTORY_SIZE - 1)]); + } + } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1566b761d23e..35a4e81eefe4 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -402,6 +402,9 @@ <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" /> + <!-- Permission required for CTS test - CtsRebootReadinessTestCases --> + <uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS index 6ba1fcb058b1..34901f5830c4 100644 --- a/packages/Shell/OWNERS +++ b/packages/Shell/OWNERS @@ -6,7 +6,6 @@ nandana@google.com svetoslavganov@google.com hackbod@google.com yamasani@google.com -moltmann@google.com toddke@google.com cbrubaker@google.com omakoto@google.com diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java index d43aaf07c6be..beee03b52579 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java @@ -46,6 +46,21 @@ public interface DetailAdapter { return true; } + /** + * @return if detail panel should animate when shown or closed + */ + default boolean shouldAnimate() { + return true; + } + + /** + * @return true if the callback handled the event and wants to keep the detail panel open, false + * otherwise. Returning false will close the panel. + */ + default boolean onDoneButtonClicked() { + return false; + } + default UiEventLogger.UiEventEnum openDetailEvent() { return INVALID; } diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml b/packages/SystemUI/res/color/kg_user_avatar_frame.xml index 4c1042e70c1a..174981e2a660 100644 --- a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml +++ b/packages/SystemUI/res/color/kg_user_avatar_frame.xml @@ -14,14 +14,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<com.android.keyguard.AlphaOptimizedLinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/keyguard_user_switcher_inner" - android:orientation="vertical" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_marginTop="@dimen/status_bar_header_height_keyguard" - android:layout_gravity="end" - android:gravity="end" - android:paddingTop="4dp"> -</com.android.keyguard.AlphaOptimizedLinearLayout> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:state_activated="true" + android:color="@color/kg_user_switcher_avatar_background" /> + <item android:color="@color/kg_user_switcher_avatar_background" /> +</selector> diff --git a/packages/SystemUI/res/drawable/end_guest_button_background.xml b/packages/SystemUI/res/drawable/end_guest_button_background.xml new file mode 100644 index 000000000000..5644b657a609 --- /dev/null +++ b/packages/SystemUI/res/drawable/end_guest_button_background.xml @@ -0,0 +1,25 @@ +<?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" + android:shape="rectangle"> + <stroke + android:width="@dimen/end_guest_button_border_size" + android:color="?android:attr/colorControlHighlight" /> + <corners android:radius="@dimen/end_guest_button_corner_radius" /> +</shape> diff --git a/packages/SystemUI/res/drawable/horizontal_ellipsis.xml b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml new file mode 100644 index 000000000000..1800857a826c --- /dev/null +++ b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml @@ -0,0 +1,27 @@ +<?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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorBackgroundFloating" > + + <path + android:pathData="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z" + android:fillColor="@android:color/white" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/kg_bg_avatar.xml b/packages/SystemUI/res/drawable/kg_bg_avatar.xml new file mode 100644 index 000000000000..addb3f7508f5 --- /dev/null +++ b/packages/SystemUI/res/drawable/kg_bg_avatar.xml @@ -0,0 +1,28 @@ +<?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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="100" + android:viewportHeight="100"> + + <path + android:fillColor="@color/kg_user_switcher_avatar_background" + android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/volume_drawer_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_bg.xml new file mode 100644 index 000000000000..f0e22926d07a --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_drawer_bg.xml @@ -0,0 +1,22 @@ +<?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" + android:paddingMode="stack" > + <size android:width="@dimen/volume_ringer_drawer_item_size" /> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml new file mode 100644 index 000000000000..5e7cb12d1c5f --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml @@ -0,0 +1,24 @@ +<?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" + android:paddingMode="stack" > + <size + android:height="@dimen/volume_ringer_drawer_item_size" + android:width="@dimen/volume_ringer_drawer_item_size" /> + <solid android:color="?android:attr/colorAccent" /> + <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml new file mode 100644 index 000000000000..2caccd9ba5c3 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml @@ -0,0 +1,55 @@ +<?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. + --> + +<!-- SeekBar drawable for volume rows. This contains a background layer (with a solid round rect, + and a bottom-aligned icon) and a progress layer (with an accent-colored round rect and icon) + that moves up and down with the progress value. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack" > + <item android:id="@android:id/background" + android:gravity="center_vertical|fill_horizontal"> + <layer-list> + <item android:id="@+id/volume_seekbar_background_solid"> + <shape> + <size android:height="@dimen/volume_dialog_panel_width" /> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners android:radius="@dimen/volume_dialog_panel_width_half" /> + </shape> + </item> + <item + android:id="@+id/volume_seekbar_background_icon" + android:gravity="center_vertical|left" + android:height="@dimen/rounded_slider_icon_size" + android:width="@dimen/rounded_slider_icon_size" + android:left="@dimen/rounded_slider_icon_inset"> + <rotate + android:fromDegrees="-270" + android:toDegrees="-270"> + <com.android.systemui.util.AlphaTintDrawableWrapper + android:drawable="@android:color/transparent" + android:tint="?android:attr/colorAccent" /> + </rotate> + </item> + </layer-list> + </item> + <item android:id="@android:id/progress" + android:gravity="center_vertical|fill_horizontal"> + <com.android.systemui.util.RoundedCornerProgressDrawable + android:drawable="@drawable/volume_row_seekbar_progress" + /> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml new file mode 100644 index 000000000000..a9a674938918 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml @@ -0,0 +1,43 @@ +<?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. + --> + +<!-- Progress drawable for volume row SeekBars. This is the accent-colored round rect that moves up + and down as the progress value changes. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true"> + <item android:id="@+id/volume_seekbar_progress_solid"> + <shape> + <size android:height="@dimen/volume_dialog_panel_width" /> + <solid android:color="?android:attr/colorAccent" /> + <corners android:radius="@dimen/volume_dialog_panel_width_half"/> + </shape> + </item> + <item + android:id="@+id/volume_seekbar_progress_icon" + android:gravity="center_vertical|right" + android:height="@dimen/rounded_slider_icon_size" + android:width="@dimen/rounded_slider_icon_size" + android:right="@dimen/rounded_slider_icon_inset"> + <rotate + android:fromDegrees="-270" + android:toDegrees="-270"> + <com.android.systemui.util.AlphaTintDrawableWrapper + android:drawable="@android:color/transparent" + android:tint="?android:attr/colorBackgroundFloating" /> + </rotate> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index c420117073c5..237dc02e5d8c 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -32,105 +32,124 @@ android:gravity="right" android:layout_gravity="right" android:background="@android:color/transparent" - android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" + android:paddingRight="@dimen/volume_dialog_stream_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:clipToPadding="false"> - <FrameLayout - android:id="@+id/ringer" - android:layout_width="@dimen/volume_dialog_ringer_size" - android:layout_height="@dimen/volume_dialog_ringer_size" - android:layout_marginBottom="@dimen/volume_dialog_spacer" - android:gravity="right" - android:layout_gravity="right" - android:translationZ="@dimen/volume_dialog_elevation" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full"> - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/ringer_icon" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="fitCenter" - android:padding="@dimen/volume_dialog_ringer_icon_padding" - android:tint="@color/accent_tint_color_selector" - android:layout_gravity="center" - android:soundEffectsEnabled="false" /> - - <include layout="@layout/volume_dnd_icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginRight="@dimen/volume_dialog_stream_padding" - android:layout_marginTop="6dp"/> - </FrameLayout> - + <!-- + Container for a) the ringer drawer and the caption button next to b) the volume rows. + --> <LinearLayout - android:id="@+id/main" android:layout_width="wrap_content" - android:minWidth="@dimen/volume_dialog_panel_width" android:layout_height="wrap_content" - android:layout_marginTop="68dp" - android:gravity="right" - android:layout_gravity="right" - android:orientation="vertical" - android:translationZ="@dimen/volume_dialog_elevation" + android:orientation="horizontal" android:clipChildren="false" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full" > - <LinearLayout - android:id="@+id/volume_dialog_rows" + android:clipToPadding="false"> + + <!-- The ringer drawer and the caption button. --> + <FrameLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minWidth="@dimen/volume_dialog_panel_width" - android:gravity="center" - android:orientation="horizontal" + android:layout_height="match_parent" android:paddingRight="@dimen/volume_dialog_stream_padding" - android:paddingLeft="@dimen/volume_dialog_stream_padding"> - <!-- volume rows added and removed here! :-) --> - </LinearLayout> + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <include layout="@layout/volume_ringer_drawer" + android:layout_gravity="top|right"/> + + <FrameLayout + android:id="@+id/odi_captions" + android:layout_width="@dimen/volume_dialog_caption_size" + android:layout_height="@dimen/volume_dialog_caption_size" + android:gravity="center" + android:layout_gravity="bottom|right" + android:layout_marginBottom="@dimen/volume_dialog_tap_target_size" + android:clipToPadding="false"> + + <com.android.systemui.volume.CaptionsToggleImageButton + android:id="@+id/odi_captions_icon" + android:src="@drawable/ic_volume_odi_captions_disabled" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:tint="@color/caption_tint_color_selector" + android:layout_gravity="center" + android:soundEffectsEnabled="false" + sysui:optedOut="false"/> + + </FrameLayout> + + </FrameLayout> + <FrameLayout - android:id="@+id/settings_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/rounded_bg_bottom_background"> + android:visibility="gone" + android:id="@+id/ringer" + android:layout_width="@dimen/volume_dialog_ringer_size" + android:layout_height="@dimen/volume_dialog_ringer_size" + android:layout_marginBottom="@dimen/volume_dialog_spacer" + android:gravity="right" + android:layout_gravity="right" + android:translationZ="@dimen/volume_dialog_elevation" + android:clipToPadding="false" + android:background="@drawable/rounded_bg_full"> <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/settings" - android:src="@drawable/ic_tune_black_16dp" - android:layout_width="@dimen/volume_dialog_tap_target_size" - android:layout_height="@dimen/volume_dialog_tap_target_size" + android:id="@+id/ringer_icon" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" + android:padding="@dimen/volume_dialog_ringer_icon_padding" + android:tint="@color/accent_tint_color_selector" android:layout_gravity="center" - android:contentDescription="@string/accessibility_volume_settings" - android:background="@drawable/ripple_drawable_20dp" - android:tint="?android:attr/textColorSecondary" android:soundEffectsEnabled="false" /> + + <include layout="@layout/volume_dnd_icon" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/volume_dialog_stream_padding" + android:layout_marginTop="6dp"/> </FrameLayout> - </LinearLayout> - <FrameLayout - android:id="@+id/odi_captions" - android:layout_width="@dimen/volume_dialog_caption_size" - android:layout_height="@dimen/volume_dialog_caption_size" - android:layout_marginRight="68dp" - android:gravity="right" - android:layout_gravity="right" - android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" - android:background="@drawable/rounded_bg_full"> - <com.android.systemui.volume.CaptionsToggleImageButton - android:id="@+id/odi_captions_icon" - android:src="@drawable/ic_volume_odi_captions_disabled" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:tint="@color/caption_tint_color_selector" - android:layout_gravity="center" - android:soundEffectsEnabled="false" - sysui:optedOut="false"/> - </FrameLayout> + <LinearLayout + android:id="@+id/main" + android:layout_width="wrap_content" + android:minWidth="@dimen/volume_dialog_panel_width" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:orientation="vertical" + android:clipChildren="false" + android:clipToPadding="false"> + <LinearLayout + android:id="@+id/volume_dialog_rows" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/volume_dialog_panel_width" + android:gravity="center" + android:orientation="horizontal"> + <!-- volume rows added and removed here! :-) --> + </LinearLayout> + <FrameLayout + android:id="@+id/settings_container" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/settings" + android:src="@drawable/horizontal_ellipsis" + android:layout_width="@dimen/volume_dialog_tap_target_size" + android:layout_height="@dimen/volume_dialog_tap_target_size" + android:layout_gravity="center" + android:contentDescription="@string/accessibility_volume_settings" + android:background="@drawable/ripple_drawable_20dp" + android:tint="?android:attr/colorBackgroundFloating" + android:soundEffectsEnabled="false" /> + </FrameLayout> + </LinearLayout> + + </LinearLayout> <ViewStub android:id="@+id/odi_captions_tooltip_stub" diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml new file mode 100644 index 000000000000..3938b73d08ff --- /dev/null +++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml @@ -0,0 +1,32 @@ +<?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 + --> +<!-- This is a view that shows a user switcher in Keyguard. --> +<com.android.systemui.statusbar.phone.UserAvatarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_qs_user_switch_view" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" + android:layout_centerHorizontal="true" + android:layout_gravity="center_horizontal|bottom" + systemui:avatarPadding="0dp" + systemui:badgeDiameter="18dp" + systemui:badgeMargin="1dp" + systemui:frameColor="@color/kg_user_avatar_frame" + systemui:framePadding="0dp" + systemui:frameWidth="0dp"> +</com.android.systemui.statusbar.phone.UserAvatarView> diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml index 416ee8147e33..2789ed125b09 100644 --- a/packages/SystemUI/res/layout/keyguard_status_bar.xml +++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml @@ -43,17 +43,12 @@ <include layout="@layout/system_icons" /> </FrameLayout> - <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch" - android:layout_width="@dimen/multi_user_switch_width_keyguard" - android:layout_height="match_parent" - android:background="@drawable/ripple_drawable" - android:layout_marginEnd="@dimen/multi_user_switch_keyguard_margin"> - <ImageView android:id="@+id/multi_user_avatar" - android:layout_width="@dimen/multi_user_avatar_keyguard_size" - android:layout_height="@dimen/multi_user_avatar_keyguard_size" - android:layout_gravity="center" - android:scaleType="centerInside"/> - </com.android.systemui.statusbar.phone.MultiUserSwitch> + + <ImageView android:id="@+id/multi_user_avatar" + android:layout_width="@dimen/multi_user_avatar_keyguard_size" + android:layout_height="@dimen/multi_user_avatar_keyguard_size" + android:layout_gravity="center" + android:scaleType="centerInside"/> </LinearLayout> <Space diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml index 983ba6d5e240..253c03e9effb 100644 --- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml +++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml @@ -14,10 +14,50 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<view xmlns:android="http://schemas.android.com/apk/res/android" - class="com.android.systemui.statusbar.policy.KeyguardUserSwitcher$Container" - android:visibility="gone" - android:layout_height="match_parent" - android:layout_width="match_parent"> - <!-- KeyguardUserSwitcher loads keyguard_user_switcher_inner.xml here --> -</view>
\ No newline at end of file +<!-- This is a view that shows a user switcher in Keyguard. --> +<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_user_switcher_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="end"> + + <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView + android:id="@+id/keyguard_user_switcher_list" + android:orientation="vertical" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="top|end" + android:gravity="end" /> + + <LinearLayout + android:id="@+id/end_guest_button" + android:layout_height="@dimen/end_guest_button_layout_height" + android:layout_width="wrap_content" + android:layout_gravity="center_horizontal|bottom" + android:layout_centerHorizontal="true" + android:layout_marginBottom="@dimen/end_guest_button_margin_bottom" + android:orientation="horizontal" + android:gravity="center" + android:paddingLeft="@dimen/end_guest_button_padding_horizontal" + android:paddingRight="@dimen/end_guest_button_padding_horizontal" + android:background="@drawable/end_guest_button_background" + android:visibility="gone"> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:src="@drawable/ic_exit_to_app" + android:background="@android:color/transparent" + android:color="?attr/wallpaperTextColor" /> + <TextView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="center" + android:fontFamily="@*android:string/config_bodyFontFamilyMedium" + android:textColor="?attr/wallpaperTextColor" + android:textSize="13sp" + android:text="@string/guest_exit_button" /> + </LinearLayout> + +</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView> diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml index 1cd1a04ab462..aaa372a5be6e 100644 --- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml +++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml @@ -19,29 +19,30 @@ <!-- LinearLayout --> <com.android.systemui.statusbar.policy.KeyguardUserDetailItemView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:sysui="http://schemas.android.com/apk/res-auto" + xmlns:systemui="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="8dp" android:layout_marginEnd="8dp" - android:gravity="center_vertical" + android:gravity="end|center_vertical" android:clickable="true" - android:background="@drawable/ripple_drawable" - sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher" - sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated"> - <TextView android:id="@+id/user_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="13dp" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher" - /> - <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture" - android:layout_width="@dimen/kg_framed_avatar_size" - android:layout_height="@dimen/kg_framed_avatar_size" - android:contentDescription="@null" - sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness" - sysui:framePadding="2.5dp" - sysui:badgeDiameter="18dp" - sysui:badgeMargin="1dp" - sysui:frameColor="@color/kg_user_switcher_rounded_background_color" /> + android:background="@drawable/kg_user_switcher_rounded_bg" + systemui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher" + systemui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"> + <TextView + android:id="@+id/user_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="20dp" + android:layout_marginEnd="16dp" /> + <com.android.systemui.statusbar.phone.UserAvatarView + android:id="@+id/user_picture" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" + systemui:avatarPadding="0dp" + systemui:badgeDiameter="18dp" + systemui:badgeMargin="1dp" + systemui:frameWidth="0dp" + systemui:framePadding="0dp" + systemui:frameColor="@color/kg_user_avatar_frame" /> </com.android.systemui.statusbar.policy.KeyguardUserDetailItemView> diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml index 8a47a22ff985..95cee66af536 100644 --- a/packages/SystemUI/res/layout/media_carousel.xml +++ b/packages/SystemUI/res/layout/media_carousel.xml @@ -47,7 +47,7 @@ android:layout_width="wrap_content" android:layout_height="48dp" android:layout_marginBottom="4dp" - android:tint="@color/media_primary_text" + android:tint="?android:attr/textColorPrimary" android:forceHasOverlappingRendering="false" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml index 6b4270531d0b..a4cf5eddb30e 100644 --- a/packages/SystemUI/res/layout/media_view.xml +++ b/packages/SystemUI/res/layout/media_view.xml @@ -48,7 +48,7 @@ android:layout_height="wrap_content" android:layout_alignParentStart="true" android:fontFamily="@*android:string/config_bodyFontFamily" - android:textColor="@color/media_primary_text" + android:textColor="?android:attr/textColorPrimary" android:gravity="start" android:textSize="14sp" /> @@ -58,7 +58,7 @@ android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:fontFamily="@*android:string/config_bodyFontFamily" - android:textColor="@color/media_primary_text" + android:textColor="?android:attr/textColorPrimary" android:gravity="end" android:textSize="14sp" /> </FrameLayout> @@ -120,13 +120,13 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="horizontal" - android:gravity="center_vertical|end" + android:gravity="center" + android:background="@drawable/qs_media_light_source" android:forceHasOverlappingRendering="false"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:foreground="@drawable/qs_media_seamless_background" - android:background="@drawable/qs_media_light_source" + android:background="@drawable/qs_media_seamless_background" android:orientation="horizontal" android:padding="6dp" android:contentDescription="@string/quick_settings_media_device_label"> @@ -135,7 +135,7 @@ android:layout_width="@dimen/qs_seamless_icon_size" android:layout_height="@dimen/qs_seamless_icon_size" android:layout_gravity="center" - android:tint="@color/media_primary_text" + android:tint="?android:attr/colorPrimary" android:src="@*android:drawable/ic_media_seamless" /> <TextView android:visibility="gone" @@ -147,7 +147,7 @@ android:fontFamily="@*android:string/config_headlineFontFamily" android:singleLine="true" android:text="@*android:string/ext_media_seamless_action" - android:textColor="@color/media_primary_text" + android:textColor="?android:attr/colorPrimary" android:textDirection="locale" android:textSize="14sp" /> </LinearLayout> @@ -157,7 +157,7 @@ android:id="@+id/media_seamless_fallback" android:layout_width="@dimen/qs_seamless_icon_size" android:layout_height="@dimen/qs_seamless_icon_size" - android:tint="@color/media_primary_text" + android:tint="?android:attr/textColorPrimary" android:src="@drawable/ic_cast_connected" android:forceHasOverlappingRendering="false" /> @@ -171,15 +171,15 @@ android:clickable="true" android:maxHeight="@dimen/qs_media_enabled_seekbar_height" android:paddingVertical="@dimen/qs_media_enabled_seekbar_vertical_padding" - android:thumbTint="@color/media_primary_text" - android:progressTint="@color/media_seekbar_progress" - android:progressBackgroundTint="@color/media_disabled" + android:thumbTint="?android:attr/textColorPrimary" + android:progressTint="?android:attr/textColorPrimary" + android:progressBackgroundTint="?android:attr/colorBackground" android:splitTrack="false" /> <!-- App name --> <TextView android:id="@+id/app_name" - android:textColor="@color/media_primary_text" + android:textColor="?android:attr/textColorPrimary" android:layout_width="0dp" android:layout_height="wrap_content" android:singleLine="true" @@ -194,7 +194,7 @@ android:layout_height="wrap_content" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:singleLine="true" - android:textColor="@color/media_primary_text" + android:textColor="?android:attr/textColorPrimary" android:textSize="16sp" /> <!-- Artist name --> @@ -204,12 +204,12 @@ android:layout_height="wrap_content" android:fontFamily="@*android:string/config_headlineFontFamily" android:singleLine="true" - android:textColor="@color/media_secondary_text" + android:textColor="?android:attr/textColorSecondary" android:textSize="14sp" /> <com.android.internal.widget.CachingIconView android:id="@+id/icon" - android:tint="@color/media_primary_text" + android:tint="?android:attr/textColorPrimary" android:layout_width="48dp" android:layout_height="48dp" android:layout_margin="6dp" /> @@ -223,7 +223,7 @@ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding" android:id="@+id/media_text" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textColor="@color/media_primary_text" + android:textColor="?android:attr/textColorSecondary" android:text="@string/controls_media_title" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -238,7 +238,7 @@ android:id="@+id/remove_text" android:fontFamily="@*android:string/config_headlineFontFamily" android:singleLine="true" - android:textColor="@color/media_primary_text" + android:textColor="?android:attr/textColorPrimary" android:text="@string/controls_media_close_session" app:layout_constraintTop_toBottomOf="@id/media_text" app:layout_constraintStart_toStartOf="parent" @@ -262,7 +262,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textColor="@android:color/white" + android:textColor="?android:attr/textColorPrimary" android:text="@string/controls_media_settings_button" /> </FrameLayout> @@ -283,7 +283,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textColor="@android:color/white" + android:textColor="?android:attr/textColorPrimary" android:text="@string/cancel" /> </FrameLayout> @@ -304,7 +304,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textColor="@android:color/white" + android:textColor="?android:attr/textColorPrimary" android:text="@string/controls_media_dismiss_button" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index d6385ffbcc0c..a8ef7c346b95 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -31,6 +31,18 @@ android:layout_height="match_parent" android:visibility="gone" /> + <ViewStub + android:id="@+id/keyguard_qs_user_switch_stub" + android:layout="@layout/keyguard_qs_user_switch" + android:layout_height="match_parent" + android:layout_width="match_parent" /> + + <ViewStub + android:id="@+id/keyguard_user_switcher_stub" + android:layout="@layout/keyguard_user_switcher" + android:layout_height="match_parent" + android:layout_width="match_parent" /> + <include layout="@layout/keyguard_status_view" android:visibility="gone" /> @@ -57,6 +69,13 @@ systemui:layout_constraintEnd_toEndOf="parent" /> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/qs_edge_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + systemui:layout_constraintGuide_percent="0.5" + android:orientation="vertical"/> + <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout android:id="@+id/notification_stack_scroller" android:layout_marginTop="@dimen/notification_panel_margin_top" @@ -72,12 +91,6 @@ <include layout="@layout/photo_preview_overlay" /> - <ViewStub - android:id="@+id/keyguard_user_switcher" - android:layout="@layout/keyguard_user_switcher" - android:layout_height="match_parent" - android:layout_width="match_parent" /> - <include layout="@layout/keyguard_status_bar" android:visibility="invisible" /> diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 1810c196c83d..6aac5a34821b 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -21,6 +21,8 @@ android:layout_height="wrap_content" android:gravity="right" android:layout_gravity="right" + android:paddingRight="@dimen/volume_dialog_stream_padding" + android:clipToPadding="false" android:background="@android:color/transparent" android:theme="@style/volume_dialog_theme"> @@ -34,13 +36,15 @@ android:layout_gravity="right" android:background="@android:color/transparent" android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:orientation="vertical" - android:clipToPadding="false"> + android:clipToPadding="false" + android:clipChildren="false"> + + <include layout="@layout/volume_ringer_drawer" /> <FrameLayout + android:visibility="gone" android:id="@+id/ringer" android:layout_width="@dimen/volume_dialog_ringer_size" android:layout_height="@dimen/volume_dialog_ringer_size" @@ -77,10 +81,8 @@ android:gravity="right" android:layout_gravity="right" android:orientation="vertical" - android:translationZ="@dimen/volume_dialog_elevation" android:clipChildren="false" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full" > + android:clipToPadding="false" > <LinearLayout android:id="@+id/volume_dialog_rows" android:layout_width="wrap_content" @@ -88,24 +90,22 @@ android:minWidth="@dimen/volume_dialog_panel_width" android:gravity="center" android:orientation="horizontal" - android:paddingRight="@dimen/volume_dialog_stream_padding" - android:paddingLeft="@dimen/volume_dialog_stream_padding"> + android:layout_marginTop="@dimen/volume_row_slider_padding_start"> <!-- volume rows added and removed here! :-) --> </LinearLayout> <FrameLayout android:id="@+id/settings_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/rounded_bg_bottom_background"> + android:layout_height="wrap_content"> <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/settings" - android:src="@drawable/ic_tune_black_16dp" + android:src="@drawable/horizontal_ellipsis" android:layout_width="@dimen/volume_dialog_tap_target_size" android:layout_height="@dimen/volume_dialog_tap_target_size" android:layout_gravity="center" android:contentDescription="@string/accessibility_volume_settings" android:background="@drawable/ripple_drawable_20dp" - android:tint="?android:attr/textColorPrimary" + android:tint="?android:attr/colorBackgroundFloating" android:soundEffectsEnabled="false" /> </FrameLayout> </LinearLayout> @@ -118,7 +118,6 @@ android:gravity="right" android:layout_gravity="right" android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" android:background="@drawable/rounded_bg_full"> <com.android.systemui.volume.CaptionsToggleImageButton android:id="@+id/odi_captions_icon" @@ -127,7 +126,7 @@ android:background="@drawable/rounded_ripple" android:layout_width="match_parent" android:layout_height="match_parent" - android:tint="?android:attr/textColorPrimary" + android:tint="?android:attr/colorAccent" android:layout_gravity="center" android:soundEffectsEnabled="false" sysui:optedOut="false"/> diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index b9efc5be70c1..fda59b50104a 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -20,11 +20,12 @@ android:layout_width="@dimen/volume_dialog_panel_width" android:clipChildren="false" android:clipToPadding="false" + android:translationZ="@dimen/volume_dialog_elevation" android:theme="@style/volume_dialog_theme"> <LinearLayout android:layout_height="wrap_content" - android:layout_width="match_parent" + android:layout_width="@dimen/volume_dialog_panel_width" android:gravity="center" android:layout_gravity="center" android:orientation="vertical" > @@ -41,21 +42,23 @@ <FrameLayout android:id="@+id/volume_row_slider_frame" android:layout_width="match_parent" - android:layout_marginTop="@dimen/volume_dialog_slider_margin_top" - android:layout_marginBottom="@dimen/volume_dialog_slider_margin_bottom" - android:layoutDirection="rtl" - android:layout_height="@dimen/volume_dialog_slider_height"> + android:layout_height="@dimen/volume_row_slider_height"> + <include layout="@layout/volume_dnd_icon"/> <SeekBar android:id="@+id/volume_row_slider" + android:paddingLeft="0dp" + android:paddingRight="0dp" + android:paddingStart="0dp" + android:paddingEnd="0dp" android:clickable="true" - android:layout_width="@dimen/volume_dialog_slider_height" + android:layout_width="@dimen/volume_row_slider_height" android:layout_height="match_parent" - android:layoutDirection="rtl" android:layout_gravity="center" - android:rotation="90" /> + android:rotation="270" /> </FrameLayout> <com.android.keyguard.AlphaOptimizedImageButton + android:visibility="gone" android:id="@+id/volume_row_icon" style="@style/VolumeButtons" android:layout_width="@dimen/volume_dialog_tap_target_size" @@ -66,6 +69,4 @@ android:soundEffectsEnabled="false" /> </LinearLayout> - <include layout="@layout/volume_dnd_icon"/> - </FrameLayout> diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml new file mode 100644 index 000000000000..d6e1782382fa --- /dev/null +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -0,0 +1,126 @@ +<?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. +--> + +<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:clipToPadding="false" + android:clipChildren="false"> + + <!-- Drawer view, invisible by default. --> + <FrameLayout + android:id="@+id/volume_drawer_container" + android:alpha="0.0" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/volume_drawer_bg" + android:orientation="vertical"> + + <!-- View that is animated to a tapped ringer selection, so it appears selected. --> + <FrameLayout + android:id="@+id/volume_drawer_selection_background" + android:alpha="0.0" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:layout_gravity="bottom|right" + android:background="@drawable/volume_drawer_selection_bg" /> + + <LinearLayout + android:id="@+id/volume_drawer_options" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/volume_drawer_vibrate" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_vibrate" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_vibrate_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer_vibrate" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/volume_drawer_mute" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_mute" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_mute_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer_mute" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/volume_drawer_normal" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_unmute" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_normal_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer" /> + + </FrameLayout> + + </LinearLayout> + + </FrameLayout> + + <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding + position in the drawer. When the drawer is closed, it animates back. --> + <FrameLayout + android:id="@+id/volume_new_ringer_active_icon_container" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:layout_gravity="bottom|right" + android:description="@string/volume_ringer_change" + android:background="@drawable/volume_drawer_selection_bg"> + + <ImageView + android:id="@+id/volume_new_ringer_active_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorBackgroundFloating" + android:src="@drawable/ic_volume_media" /> + + </FrameLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index 3153d0d0123d..37ec576be4be 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -89,6 +89,8 @@ <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color> <!-- Icon color for selected user avatars in keyguard user switcher --> <color name="kg_user_switcher_selected_avatar_icon_color">#202124</color> + <!-- Color of background circle of user avatars in keyguard user switcher --> + <color name="kg_user_switcher_avatar_background">#3C4043</color> <!-- Icon color for user avatars in quick settings user switcher --> <color name="qs_user_switcher_avatar_icon_color">@android:color/background_light</color> <!-- Icon color for selected user avatars in quick settings user switcher --> diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml index 02bd60210e81..ee2b82dca811 100644 --- a/packages/SystemUI/res/values-sw600dp/styles.xml +++ b/packages/SystemUI/res/values-sw600dp/styles.xml @@ -23,13 +23,6 @@ <item name="numColumns">4</item> </style> - <style name="TextAppearance.StatusBar.Expanded.UserSwitcher"> - <item name="android:textSize">@dimen/kg_user_switcher_text_size</item> - <item name="android:textStyle">normal</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">?attr/wallpaperTextColor</item> - </style> - <style name="TextAppearance.QS.UserSwitcher"> <item name="android:textSize">@dimen/kg_user_switcher_text_size</item> <item name="android:textColor">?android:attr/textColorSecondary</item> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 8166e35d5b6a..a1191aeacdde 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -139,6 +139,10 @@ <!-- Size of shadows/elevations on keyguard --> <attr name="shadowRadius" format="float" /> + <attr name="handleThickness" format="dimension" /> + <attr name="handleColor" format="color" /> + <attr name="scrimColor" format="color" /> + <!-- Used display CarrierText in Keyguard or QS Footer --> <declare-styleable name="CarrierText"> <attr name="allCaps" format="boolean" /> @@ -173,15 +177,15 @@ </declare-styleable> <declare-styleable name="CropView"> - <attr name="handleThickness" format="dimension" /> - <attr name="handleColor" format="color" /> - <attr name="scrimColor" format="color" /> + <attr name="handleThickness" /> + <attr name="handleColor" /> + <attr name="scrimColor" /> </declare-styleable> <declare-styleable name="MagnifierView"> - <attr name="handleThickness" format="dimension" /> - <attr name="handleColor" format="color" /> - <attr name="scrimColor" format="color" /> + <attr name="handleThickness" /> + <attr name="handleColor" /> + <attr name="scrimColor" /> <attr name="borderThickness" format="dimension" /> <attr name="borderColor" format="color" /> </declare-styleable> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 5fb6de7bb588..acd671cb6297 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -66,9 +66,13 @@ <!-- Color for rounded background for activated user in keyguard user switcher --> <color name="kg_user_switcher_activated_background_color">#26000000</color> <!-- Icon color for user avatars in keyguard user switcher --> - <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color> - <!-- Icon color for selected user avatars in keyguard user switcher --> - <color name="kg_user_switcher_selected_avatar_icon_color">@android:color/background_light</color> + <color name="kg_user_switcher_avatar_icon_color">@color/GM2_grey_800</color> + <!-- Icon color for user avatars in keyguard user switcher that restricted + (e.g. cannot be switched to) --> + <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color> + <!-- Color of background circle of user avatars in keyguard user switcher --> + <color name="kg_user_switcher_avatar_background">@color/GM2_grey_300</color> + <!-- Icon color for user avatars in user switcher quick settings --> <color name="qs_user_switcher_avatar_icon_color">#3C4043</color> <!-- Icon color for selected user avatars in user switcher quick settings --> @@ -240,11 +244,8 @@ <color name="magnification_switch_button_color">#7F000000</color> <!-- media --> - <color name="media_primary_text">@android:color/white</color> - <color name="media_secondary_text">#99ffffff</color> <!-- 60% --> - <color name="media_seekbar_progress">#c0ffffff</color> <color name="media_disabled">#80ffffff</color> - <color name="media_seamless_border">#26ffffff</color> <!-- 15% --> + <color name="media_seamless_border">?android:attr/colorAccent</color> <color name="media_divider">#1d000000</color> <!-- controls --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 61962256f93d..78927f8bf8d4 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -279,6 +279,11 @@ <!-- Whether to show the full screen user switcher. --> <bool name="config_enableFullscreenUserSwitcher">false</bool> + <!-- Whether the multi-user switch on the keyguard opens QS user panel. If false, clicking the + user switch on the keyguard will replace the notifications and status area with the user + switcher. The multi-user switch is only shown if config_keyguardUserSwitcher=false. --> + <bool name="config_keyguard_user_switch_opens_qs_details">false</bool> + <!-- SystemUIFactory component --> <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 08cd6553e252..2fd8f3f04b6e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -456,10 +456,12 @@ <dimen name="volume_dialog_panel_transparent_padding">20dp</dimen> - <dimen name="volume_dialog_stream_padding">8dp</dimen> + <dimen name="volume_dialog_stream_padding">12dp</dimen> <dimen name="volume_dialog_panel_width">64dp</dimen> + <dimen name="volume_dialog_panel_width_half">32dp</dimen> + <dimen name="volume_dialog_slider_height">116dp</dimen> <dimen name="volume_dialog_ringer_size">64dp</dimen> @@ -486,6 +488,13 @@ <dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen> + <!-- Size of each item in the ringer selector drawer. --> + <dimen name="volume_ringer_drawer_item_size">64dp</dimen> + <dimen name="volume_ringer_drawer_item_size_half">32dp</dimen> + + <!-- Size of the icon inside each item in the ringer selector drawer. --> + <dimen name="volume_ringer_drawer_icon_size">24dp</dimen> + <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -743,9 +752,6 @@ <!-- end margin for system icons if multi user switch is hidden --> <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen> - <!-- The thickness of the colored border around the current user. --> - <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen> - <dimen name="data_usage_graph_marker_width">4dp</dimen> <!-- The padding bottom of the clock group when QS is expanded. --> @@ -805,7 +811,7 @@ <!-- Size of user icon + frame in the qs user picker (incl. frame) --> <dimen name="qs_framed_avatar_size">54dp</dimen> <!-- Size of user icon + frame in the keyguard user picker (incl. frame) --> - <dimen name="kg_framed_avatar_size">54dp</dimen> + <dimen name="kg_framed_avatar_size">48dp</dimen> <!-- Margin on the left side of the carrier text on Keyguard --> <dimen name="keyguard_carrier_text_margin">16dp</dimen> @@ -969,7 +975,7 @@ <dimen name="volume_row_padding_start">4dp</dimen> <dimen name="volume_row_header_padding_start">16dp</dimen> <dimen name="volume_row_height">64dp</dimen> - <dimen name="volume_row_slider_height">48dp</dimen> + <dimen name="volume_row_slider_height">192dp</dimen> <dimen name="volume_row_slider_padding_start">12dp</dimen> <dimen name="volume_expander_margin_end">2dp</dimen> @@ -1324,8 +1330,16 @@ <dimen name="screenrecord_status_icon_height">17.5dp</dimen> <dimen name="screenrecord_status_icon_bg_radius">8dp</dimen> + <!-- Keyguard user switcher --> <dimen name="kg_user_switcher_text_size">16sp</dimen> + <!-- End guest session button --> + <dimen name="end_guest_button_layout_height">32dp</dimen> + <dimen name="end_guest_button_padding_horizontal">16dp</dimen> + <dimen name="end_guest_button_margin_bottom">96dp</dimen> + <dimen name="end_guest_button_border_size">1dp</dimen> + <dimen name="end_guest_button_corner_radius">16dp</dimen> + <!-- Opacity at which the background for the shutdown UI will be drawn. --> <item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 6196e4a6613a..d4bb128120e9 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -18,11 +18,12 @@ <resources> <bool name="are_flags_overrideable">false</bool> - <bool name="flag_notification_pipeline2">false</bool> + <bool name="flag_notification_pipeline2">true</bool> <bool name="flag_notification_pipeline2_rendering">false</bool> <bool name="flag_notif_updates">false</bool> <bool name="flag_shade_is_opaque">false</bool> + <bool name="flag_monet">false</bool> <!-- b/171917882 --> <bool name="flag_notification_twocolumn">false</bool> @@ -34,11 +35,11 @@ <bool name="flag_brightness_slider">false</bool> - <!-- The new animations to/from lockscreen and AOD! --> - <bool name="flag_lockscreen_animations">false</bool> - <!-- People Tile flag --> <bool name="flag_conversations">false</bool> + <!-- The new animations to/from lockscreen and AOD! --> + <bool name="flag_lockscreen_animations">false</bool> + <bool name="flag_toast_style">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index abcf4e802ab9..f8cad7e6f3a7 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1112,14 +1112,17 @@ <!-- Name for a freshly added user [CHAR LIMIT=30] --> <string name="user_new_user_name">New user</string> + <!-- Label for button that exits guest session and clears the guest user data [CHAR LIMIT=50]--> + <string name="guest_exit_button">End guest session</string> + <!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] --> - <string name="guest_exit_guest_dialog_title">End guest session?</string> + <string name="guest_exit_guest_dialog_title">Remove guest?</string> <!-- Message of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] --> <string name="guest_exit_guest_dialog_message">All apps and data in this session will be deleted.</string> <!-- Label for button in confirmation dialog when exiting guest session [CHAR LIMIT=35] --> - <string name="guest_exit_guest_dialog_remove">End session</string> + <string name="guest_exit_guest_dialog_remove">Remove</string> <!-- Title of the notification when resuming an existing guest session [CHAR LIMIT=NONE] --> <string name="guest_wipe_session_title">Welcome back, guest!</string> @@ -1548,6 +1551,8 @@ <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string> <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string> + <string name="volume_ringer_change">Tap to change ringer mode</string> + <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] --> <string name="volume_ringer_hint_mute">mute</string> <!-- Hint for accessibility. For example: double tap to unmute [CHAR_LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index afdf23b14a7a..14b376a8bf6c 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -114,12 +114,12 @@ <style name="TextAppearance.StatusBar.Expanded.UserSwitcher"> <item name="android:textSize">@dimen/kg_user_switcher_text_size</item> <item name="android:textStyle">normal</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:textColor">?attr/wallpaperTextColor</item> </style> <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated"> <item name="android:fontWeight">700</item> - <item name="android:textStyle">bold</item> </style> <style name="TextAppearance" /> @@ -582,7 +582,7 @@ <style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small"> <item name="android:background">@drawable/qs_media_light_source</item> - <item name="android:tint">@android:color/white</item> + <item name="android:tint">?android:attr/textColorPrimary</item> <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item> </style> diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml index f834d6df15c2..f83e3a1d9b26 100644 --- a/packages/SystemUI/res/xml/media_collapsed.xml +++ b/packages/SystemUI/res/xml/media_collapsed.xml @@ -36,25 +36,23 @@ app:layout_constraintTop_toTopOf="@id/icon" app:layout_constraintBottom_toBottomOf="@id/icon" app:layout_constraintStart_toEndOf="@id/icon" - app:layout_constraintEnd_toStartOf="@id/media_seamless" - app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline" app:layout_constrainedWidth="true" app:layout_constraintHorizontal_bias="0" /> <Constraint android:id="@+id/media_seamless" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toEndOf="@id/app_name" + app:layout_constraintStart_toEndOf="@id/center_vertical_guideline" app:layout_constraintHorizontal_chainStyle="spread_inside" app:layout_constraintHorizontal_bias="1" app:layout_constrainedWidth="true" app:layout_constraintWidth_min="48dp" app:layout_constraintHeight_min="48dp" - android:layout_marginEnd="@dimen/qs_center_guideline_padding" android:layout_marginStart="@dimen/qs_center_guideline_padding" /> diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml index d89e0eb4df63..7c6772059695 100644 --- a/packages/SystemUI/res/xml/media_expanded.xml +++ b/packages/SystemUI/res/xml/media_expanded.xml @@ -36,25 +36,23 @@ app:layout_constraintTop_toTopOf="@id/icon" app:layout_constraintBottom_toBottomOf="@id/icon" app:layout_constraintStart_toEndOf="@id/icon" - app:layout_constraintEnd_toStartOf="@id/media_seamless" - app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline" app:layout_constrainedWidth="true" app:layout_constraintHorizontal_bias="0" /> <Constraint android:id="@+id/media_seamless" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toEndOf="@id/app_name" + app:layout_constraintStart_toEndOf="@id/center_vertical_guideline" app:layout_constraintHorizontal_chainStyle="spread_inside" app:layout_constraintHorizontal_bias="1" app:layout_constrainedWidth="true" app:layout_constraintWidth_min="48dp" app:layout_constraintHeight_min="48dp" - android:layout_marginEnd="@dimen/qs_center_guideline_padding" android:layout_marginStart="@dimen/qs_center_guideline_padding" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index a5f364d30d7d..6fb6760be653 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -16,13 +16,9 @@ package com.android.keyguard; -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; - import android.util.Slog; import android.view.View; -import com.android.systemui.Interpolators; -import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -50,13 +46,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardSliceViewController mKeyguardSliceViewController; private final KeyguardClockSwitchController mKeyguardClockSwitchController; - private final KeyguardStateController mKeyguardStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; private final NotificationIconAreaController mNotificationIconAreaController; private final DozeParameters mDozeParameters; + private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; - private boolean mKeyguardStatusViewVisibilityAnimating; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; @Inject @@ -72,11 +67,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; - mKeyguardStateController = keyguardStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; mNotificationIconAreaController = notificationIconAreaController; mDozeParameters = dozeParameters; + mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, + dozeParameters); } @Override @@ -144,7 +140,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Set keyguard status view alpha. */ public void setAlpha(float alpha) { - if (!mKeyguardStatusViewVisibilityAnimating) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { mView.setAlpha(alpha); } } @@ -200,7 +196,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV public void updatePosition(int x, int y, float scale, boolean animate) { // We animate the status view visible/invisible using Y translation, so don't change it // while the animation is running. - if (!mKeyguardStatusViewVisibilityAnimating) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES, animate); } @@ -230,69 +226,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState) { - mView.animate().cancel(); - mKeyguardStatusViewVisibilityAnimating = false; - if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD - && statusBarState != KEYGUARD) || goingToFullShade) { - mKeyguardStatusViewVisibilityAnimating = true; - mView.animate() - .alpha(0f) - .setStartDelay(0) - .setDuration(160) - .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction( - mAnimateKeyguardStatusViewGoneEndRunnable); - if (keyguardFadingAway) { - mView.animate() - .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay()) - .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration()) - .start(); - } - } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { - mView.setVisibility(View.VISIBLE); - mKeyguardStatusViewVisibilityAnimating = true; - mView.setAlpha(0f); - mView.animate() - .alpha(1f) - .setStartDelay(0) - .setDuration(320) - .setInterpolator(Interpolators.ALPHA_IN) - .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); - } else if (statusBarState == KEYGUARD) { - if (keyguardFadingAway) { - mKeyguardStatusViewVisibilityAnimating = true; - mView.animate() - .alpha(0) - .translationYBy(-getHeight() * 0.05f) - .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) - .setDuration(125) - .setStartDelay(0) - .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable) - .start(); - } else if (mDozeParameters.shouldControlUnlockedScreenOff()) { - mKeyguardStatusViewVisibilityAnimating = true; - - mView.setVisibility(View.VISIBLE); - mView.setAlpha(0f); - - float curTranslationY = mView.getTranslationY(); - mView.setTranslationY(curTranslationY - getHeight() * 0.1f); - mView.animate() - .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f)) - .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(1f) - .translationY(curTranslationY) - .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) - .start(); - } else { - mView.setVisibility(View.VISIBLE); - mView.setAlpha(1f); - } - } else { - mView.setVisibility(View.GONE); - mView.setAlpha(1f); - } + mKeyguardVisibilityHelper.setViewVisibility( + statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); } private void refreshTime() { @@ -393,19 +328,4 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mView.updateLogoutView(); } }; - - private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> { - mKeyguardStatusViewVisibilityAnimating = false; - mView.setVisibility(View.INVISIBLE); - }; - - - private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> { - mKeyguardStatusViewVisibilityAnimating = false; - mView.setVisibility(View.GONE); - }; - - private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { - mKeyguardStatusViewVisibilityAnimating = false; - }; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java new file mode 100644 index 000000000000..724e1f660fb9 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; + +import android.view.View; + +import com.android.systemui.Interpolators; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +/** + * Helper class for updating visibility of keyguard views based on keyguard and status bar state. + * This logic is shared by both the keyguard status view and the keyguard user switcher. + */ +public class KeyguardVisibilityHelper { + + private View mView; + private final KeyguardStateController mKeyguardStateController; + private final DozeParameters mDozeParameters; + private boolean mKeyguardViewVisibilityAnimating; + + public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController, + DozeParameters dozeParameters) { + mView = view; + mKeyguardStateController = keyguardStateController; + mDozeParameters = dozeParameters; + } + + public boolean isVisibilityAnimating() { + return mKeyguardViewVisibilityAnimating; + } + + /** + * Set the visibility of a keyguard view based on some new state. + */ + public void setViewVisibility( + int statusBarState, + boolean keyguardFadingAway, + boolean goingToFullShade, + int oldStatusBarState) { + mView.animate().cancel(); + mKeyguardViewVisibilityAnimating = false; + if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD + && statusBarState != KEYGUARD) || goingToFullShade) { + mKeyguardViewVisibilityAnimating = true; + mView.animate() + .alpha(0f) + .setStartDelay(0) + .setDuration(160) + .setInterpolator(Interpolators.ALPHA_OUT) + .withEndAction( + mAnimateKeyguardStatusViewGoneEndRunnable); + if (keyguardFadingAway) { + mView.animate() + .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay()) + .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration()) + .start(); + } + } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { + mView.setVisibility(View.VISIBLE); + mKeyguardViewVisibilityAnimating = true; + mView.setAlpha(0f); + mView.animate() + .alpha(1f) + .setStartDelay(0) + .setDuration(320) + .setInterpolator(Interpolators.ALPHA_IN) + .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); + } else if (statusBarState == KEYGUARD) { + if (keyguardFadingAway) { + mKeyguardViewVisibilityAnimating = true; + mView.animate() + .alpha(0) + .translationYBy(-mView.getHeight() * 0.05f) + .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) + .setDuration(125) + .setStartDelay(0) + .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable) + .start(); + } else if (mDozeParameters.shouldControlUnlockedScreenOff()) { + mKeyguardViewVisibilityAnimating = true; + + mView.setVisibility(View.VISIBLE); + mView.setAlpha(0f); + + float curTranslationY = mView.getTranslationY(); + mView.setTranslationY(curTranslationY - mView.getHeight() * 0.1f); + mView.animate() + .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f)) + .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(1f) + .translationY(curTranslationY) + .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) + .start(); + } else { + mView.setVisibility(View.VISIBLE); + mView.setAlpha(1f); + } + } else { + mView.setVisibility(View.GONE); + mView.setAlpha(1f); + } + } + + private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> { + mKeyguardViewVisibilityAnimating = false; + mView.setVisibility(View.INVISIBLE); + }; + + private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> { + mKeyguardViewVisibilityAnimating = false; + mView.setVisibility(View.GONE); + }; + + private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { + mKeyguardViewVisibilityAnimating = false; + }; +} diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java new file mode 100644 index 000000000000..3a0357d2a284 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.dagger; + +import com.android.systemui.statusbar.phone.UserAvatarView; +import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; + +import dagger.BindsInstance; +import dagger.Subcomponent; + +/** + * Subcomponent for helping work with KeyguardQsUserSwitch and its children. + */ +@Subcomponent(modules = {KeyguardUserSwitcherModule.class}) +@KeyguardUserSwitcherScope +public interface KeyguardQsUserSwitchComponent { + /** Simple factory for {@link KeyguardUserSwitcherComponent}. */ + @Subcomponent.Factory + interface Factory { + KeyguardQsUserSwitchComponent build( + @BindsInstance UserAvatarView userAvatarView); + } + + /** Builds a {@link KeyguardQsUserSwitchController}. */ + KeyguardQsUserSwitchController getKeyguardQsUserSwitchController(); +} diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java new file mode 100644 index 000000000000..730c14dc9600 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.dagger; + +import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; +import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; + +import dagger.BindsInstance; +import dagger.Subcomponent; + +/** + * Subcomponent for helping work with KeyguardUserSwitcher and its children. + */ +@Subcomponent(modules = {KeyguardUserSwitcherModule.class}) +@KeyguardUserSwitcherScope +public interface KeyguardUserSwitcherComponent { + /** Simple factory for {@link KeyguardUserSwitcherComponent}. */ + @Subcomponent.Factory + interface Factory { + KeyguardUserSwitcherComponent build( + @BindsInstance KeyguardUserSwitcherView keyguardUserSwitcherView); + } + + /** Builds a {@link com.android.systemui.statusbar.policy.KeyguardUserSwitcherController}. */ + KeyguardUserSwitcherController getKeyguardUserSwitcherController(); +} diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java new file mode 100644 index 000000000000..b9184f405bf9 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.dagger; + +import dagger.Module; + +/** Dagger module for {@link KeyguardUserSwitcherComponent}. */ +@Module +public abstract class KeyguardUserSwitcherModule { +} diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java new file mode 100644 index 000000000000..864472e53ce7 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +/** + * Scope annotation for singleton items within the KeyguardUserSwitcherComponent. + */ +@Documented +@Retention(RUNTIME) +@Scope +public @interface KeyguardUserSwitcherScope {} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 247f25e1ccea..6b300f4e07e4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -30,6 +30,7 @@ import android.service.controls.actions.CommandAction import android.service.controls.actions.FloatAction import android.util.Log import android.view.HapticFeedbackConstants +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -71,7 +72,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { - bouncerOrRun(Action(cvh.cws.ci.controlId, { + bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) cvh.action(BooleanAction(templateId, !isChecked)) }, true /* blockable */)) @@ -79,7 +80,7 @@ class ControlActionCoordinatorImpl @Inject constructor( override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) { val blockable = cvh.usePanel() - bouncerOrRun(Action(cvh.cws.ci.controlId, { + bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) if (cvh.usePanel()) { showDialog(cvh, control.getAppIntent().getIntent()) @@ -98,13 +99,13 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) { - bouncerOrRun(Action(cvh.cws.ci.controlId, { + bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.action(FloatAction(templateId, newValue)) }, false /* blockable */)) } override fun longPress(cvh: ControlViewHolder) { - bouncerOrRun(Action(cvh.cws.ci.controlId, { + bouncerOrRun(createAction(cvh.cws.ci.controlId, { // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -114,6 +115,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun runPendingAction(controlId: String) { + if (!keyguardStateController.isUnlocked()) return if (pendingAction?.controlId == controlId) { pendingAction?.invoke() pendingAction = null @@ -135,7 +137,8 @@ class ControlActionCoordinatorImpl @Inject constructor( false } - private fun bouncerOrRun(action: Action) { + @VisibleForTesting + fun bouncerOrRun(action: Action) { if (keyguardStateController.isShowing()) { var closeDialog = !keyguardStateController.isUnlocked() if (closeDialog) { @@ -190,6 +193,10 @@ class ControlActionCoordinatorImpl @Inject constructor( } } + @VisibleForTesting + fun createAction(controlId: String, f: () -> Unit, blockable: Boolean) = + Action(controlId, f, blockable) + inner class Action(val controlId: String, val f: () -> Unit, val blockable: Boolean) { fun invoke() { if (!blockable || shouldRunAction(controlId)) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c55fdf4783e3..91cf7108c728 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -415,6 +415,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, @Override public void onUserSwitching(int userId) { + if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); // Note that the mLockPatternUtils user has already been updated from setCurrentUser. // We need to force a reset of the views, since lockNow (called by // ActivityManagerService) will not reconstruct the keyguard if it is already showing. @@ -432,6 +433,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, @Override public void onUserSwitchComplete(int userId) { + if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); if (userId != UserHandle.USER_SYSTEM) { UserInfo info = UserManager.get(mContext).getUserInfo(userId); // Don't try to dismiss if the user has Pin/Patter/Password set diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 76281d8c0f00..de2e7c476e18 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -29,7 +29,9 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; +import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingModule; @@ -61,7 +63,8 @@ import dagger.Provides; /** * Dagger Module providing {@link StatusBar}. */ -@Module(subcomponents = {KeyguardStatusViewComponent.class}, +@Module(subcomponents = {KeyguardStatusViewComponent.class, + KeyguardQsUserSwitchComponent.class, KeyguardUserSwitcherComponent.class}, includes = {FalsingModule.class}) public class KeyguardModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt index e8dba8f3b585..c1db8edf4119 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -17,7 +17,6 @@ package com.android.systemui.log import android.util.Log -import com.android.systemui.dump.DumpManager import com.android.systemui.log.dagger.LogModule import java.io.PrintWriter import java.text.SimpleDateFormat @@ -58,7 +57,7 @@ import java.util.Locale * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or * the first letter of any of the previous. * - * Buffers are provided by [LogModule]. + * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory]. * * @param name The name of this buffer * @param maxLogs The maximum number of messages to keep in memory at any one time, including the @@ -77,10 +76,6 @@ class LogBuffer( var frozen = false private set - fun attach(dumpManager: DumpManager) { - dumpManager.registerBuffer(name, this) - } - /** * Logs a message to the log buffer * diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt new file mode 100644 index 000000000000..0622df3dbb43 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import javax.inject.Inject + +@SysUISingleton +class LogBufferFactory @Inject constructor( + private val dumpManager: DumpManager, + private val logcatEchoTracker: LogcatEchoTracker +) { + @JvmOverloads + fun create(name: String, maxPoolSize: Int, flexSize: Int = 10): LogBuffer { + val buffer = LogBuffer(name, maxPoolSize, flexSize, logcatEchoTracker) + dumpManager.registerBuffer(name, buffer) + return buffer + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index fff185b99a1e..19193f9eceb2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -22,8 +22,8 @@ import android.os.Looper; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.DumpManager; import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.LogBufferFactory; import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.log.LogcatEchoTrackerDebug; import com.android.systemui.log.LogcatEchoTrackerProd; @@ -40,96 +40,64 @@ public class LogModule { @Provides @SysUISingleton @DozeLog - public static LogBuffer provideDozeLogBuffer( - LogcatEchoTracker bufferFilter, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) { + return factory.create("DozeLog", 100); } /** Provides a logging buffer for all logs related to the data layer of notifications. */ @Provides @SysUISingleton @NotificationLog - public static LogBuffer provideNotificationsLogBuffer( - LogcatEchoTracker bufferFilter, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("NotifLog", 1000, 10, bufferFilter); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) { + return factory.create("NotifLog", 1000); } /** Provides a logging buffer for all logs related to managing notification sections. */ @Provides @SysUISingleton @NotificationSectionLog - public static LogBuffer provideNotificationSectionLogBuffer( - LogcatEchoTracker bufferFilter, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("NotifSectionLog", 1000, 10, bufferFilter); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) { + return factory.create("NotifSectionLog", 1000); } /** Provides a logging buffer for all logs related to the data layer of notifications. */ @Provides @SysUISingleton @NotifInteractionLog - public static LogBuffer provideNotifInteractionLogBuffer( - LogcatEchoTracker echoTracker, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer provideNotifInteractionLogBuffer(LogBufferFactory factory) { + return factory.create("NotifInteractionLog", 50); } /** Provides a logging buffer for all logs related to Quick Settings. */ @Provides @SysUISingleton @QSLog - public static LogBuffer provideQuickSettingsLogBuffer( - LogcatEchoTracker bufferFilter, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("QSLog", 500, 10, bufferFilter); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) { + return factory.create("QSLog", 500); } /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */ @Provides @SysUISingleton @BroadcastDispatcherLog - public static LogBuffer provideBroadcastDispatcherLogBuffer( - LogcatEchoTracker bufferFilter, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) { + return factory.create("BroadcastDispatcherLog", 500); } /** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */ @Provides @SysUISingleton @ToastLog - public static LogBuffer provideToastLogBuffer( - LogcatEchoTracker bufferFilter, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer provideToastLogBuffer(LogBufferFactory factory) { + return factory.create("ToastLog", 50); } /** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */ @Provides @SysUISingleton @PrivacyLog - public static LogBuffer providePrivacyLogBuffer( - LogcatEchoTracker bufferFilter, - DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter); - buffer.attach(dumpManager); - return buffer; + public static LogBuffer providePrivacyLogBuffer(LogBufferFactory factory) { + return factory.create("PrivacyLog", 100); } /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 935352665314..a3ff3753cbf9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -1,7 +1,9 @@ package com.android.systemui.media +import android.animation.ArgbEvaluator import android.content.Context import android.content.Intent +import android.content.res.ColorStateList import android.content.res.Configuration import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.util.Log @@ -112,6 +114,9 @@ class MediaCarouselController @Inject constructor( private val visualStabilityCallback: VisualStabilityManager.Callback private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() + private var bgColor = getBackgroundColor() + private var fgColor = com.android.settingslib.Utils.getColorAttr(context, + com.android.internal.R.attr.textColorPrimary).defaultColor private var isRtl: Boolean = false set(value) { if (value != field) { @@ -147,7 +152,7 @@ class MediaCarouselController @Inject constructor( } override fun onUiModeChanged() { - // Only settings button needs to update for dark theme + recreatePlayers() inflateSettingsButton() } } @@ -249,6 +254,11 @@ class MediaCarouselController @Inject constructor( } private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) { + data.actions.forEach { + it.icon?.setTintList(ColorStateList.valueOf(fgColor)) + } + data.appIcon?.setTintList(ColorStateList.valueOf(fgColor)) + val dataCopy = data.copy(backgroundColor = bgColor) val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey) if (existingPlayer == null) { var newPlayer = mediaControlPanelFactory.get() @@ -257,14 +267,14 @@ class MediaCarouselController @Inject constructor( val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) newPlayer.view?.player?.setLayoutParams(lp) - newPlayer.bind(data, key) + newPlayer.bind(dataCopy, key) newPlayer.setListening(currentlyExpanded) - MediaPlayerData.addMediaPlayer(key, data, newPlayer) + MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer) updatePlayerToState(newPlayer, noAnimation = true) reorderAllPlayers() } else { - existingPlayer.bind(data, key) - MediaPlayerData.addMediaPlayer(key, data, existingPlayer) + existingPlayer.bind(dataCopy, key) + MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer) if (visualStabilityManager.isReorderingAllowed) { reorderAllPlayers() } else { @@ -298,12 +308,27 @@ class MediaCarouselController @Inject constructor( } private fun recreatePlayers() { + bgColor = getBackgroundColor() + + fgColor = com.android.settingslib.Utils.getColorAttr(context, + com.android.internal.R.attr.textColorPrimary).defaultColor + pageIndicator.tintList = ColorStateList.valueOf(fgColor) + MediaPlayerData.mediaData().forEach { (key, data) -> removePlayer(key, dismissMediaData = false) addOrUpdatePlayer(key = key, oldKey = null, data = data) } } + private fun getBackgroundColor(): Int { + val themeAccent = com.android.settingslib.Utils.getColorAttr(context, + com.android.internal.R.attr.colorAccent).defaultColor + val themeBackground = com.android.settingslib.Utils.getColorAttr(context, + com.android.internal.R.attr.colorBackground).defaultColor + // Simulate transparency - cannot be actually transparent because of lockscreen + return ArgbEvaluator().evaluate(0.25f, themeBackground, themeAccent) as Int + } + private fun updatePageIndicator() { val numPages = mediaContent.getChildCount() pageIndicator.setNumPages(numPages) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 3629d4d6141c..55c55b97db51 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -25,7 +25,6 @@ import android.content.Intent import android.content.IntentFilter import android.graphics.Bitmap import android.graphics.Canvas -import android.graphics.Color import android.graphics.ImageDecoder import android.graphics.drawable.Drawable import android.graphics.drawable.Icon @@ -38,7 +37,6 @@ import android.os.UserHandle import android.service.notification.StatusBarNotification import android.text.TextUtils import android.util.Log -import com.android.internal.graphics.ColorUtils import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher @@ -48,7 +46,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState -import com.android.systemui.statusbar.notification.MediaNotificationProcessor import com.android.systemui.statusbar.notification.row.HybridGroupManager import com.android.systemui.util.Assert import com.android.systemui.util.Utils @@ -68,10 +65,6 @@ private val ART_URIS = arrayOf( private const val TAG = "MediaDataManager" private const val DEBUG = true -private const val DEFAULT_LUMINOSITY = 0.25f -private const val LUMINOSITY_THRESHOLD = 0.05f -private const val SATURATION_MULTIPLIER = 0.8f -const val DEFAULT_COLOR = Color.DKGRAY private val LOADING = MediaData(-1, false, 0, null, null, null, null, null, emptyList(), emptyList(), "INVALID", null, null, null, true, null) @@ -110,6 +103,11 @@ class MediaDataManager( private val useQsMediaPlayer: Boolean ) : Dumpable { + private val themeText = com.android.settingslib.Utils.getColorAttr(context, + com.android.internal.R.attr.textColorPrimary).defaultColor + private val bgColor = com.android.settingslib.Utils.getColorAttr(context, + com.android.internal.R.attr.colorBackground).defaultColor + // Internal listeners are part of the internal pipeline. External listeners (those registered // with [MediaDeviceManager.addListener]) receive events after they have propagated through // the internal pipeline. @@ -395,7 +393,6 @@ class MediaDataManager( } else { null } - val bgColor = artworkBitmap?.let { computeBackgroundColor(it) } ?: DEFAULT_COLOR val mediaAction = getResumeMediaAction(resumeAction) foregroundExecutor.execute { @@ -449,7 +446,6 @@ class MediaDataManager( } } } - val bgColor = computeBackgroundColor(artworkBitmap) // App name val builder = Notification.Builder.recoverBuilder(context, notif) @@ -506,7 +502,7 @@ class MediaDataManager( Icon.createWithResource(packageContext, action.getIcon()!!.getResId()) } else { action.getIcon() - } + }.setTint(themeText) val mediaAction = MediaAction( mediaActionIcon, runnable, @@ -589,38 +585,9 @@ class MediaDataManager( } } - private fun computeBackgroundColor(artworkBitmap: Bitmap?): Int { - var color = Color.WHITE - if (artworkBitmap != null && artworkBitmap.width > 1 && artworkBitmap.height > 1) { - // If we have valid art, get colors from that - val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap) - .generate() - val swatch = MediaNotificationProcessor.findBackgroundSwatch(p) - color = swatch.rgb - } else { - return DEFAULT_COLOR - } - // Adapt background color, so it's always subdued and text is legible - val tmpHsl = floatArrayOf(0f, 0f, 0f) - ColorUtils.colorToHSL(color, tmpHsl) - - val l = tmpHsl[2] - // Colors with very low luminosity can have any saturation. This means that changing the - // luminosity can make a black become red. Let's remove the saturation of very light or - // very dark colors to avoid this issue. - if (l < LUMINOSITY_THRESHOLD || l > 1f - LUMINOSITY_THRESHOLD) { - tmpHsl[1] = 0f - } - tmpHsl[1] *= SATURATION_MULTIPLIER - tmpHsl[2] = DEFAULT_LUMINOSITY - - color = ColorUtils.HSLToColor(tmpHsl) - return color - } - private fun getResumeMediaAction(action: Runnable): MediaAction { return MediaAction( - Icon.createWithResource(context, R.drawable.lb_ic_play), + Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText), action, context.getString(R.string.controls_media_resume) ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 619729e55314..9967936ac1bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -72,6 +72,7 @@ public class QSDetail extends LinearLayout { private boolean mFullyExpanded; private QuickStatusBarHeader mHeader; private boolean mTriggeredExpand; + private boolean mShouldAnimate; private int mOpenX; private int mOpenY; private boolean mAnimatingOpen; @@ -108,16 +109,6 @@ public class QSDetail extends LinearLayout { updateDetailText(); mClipper = new QSDetailClipper(this); - - final OnClickListener doneListener = new OnClickListener() { - @Override - public void onClick(View v) { - announceForAccessibility( - mContext.getString(R.string.accessibility_desc_quick_settings)); - mQsPanelController.closeDetail(); - } - }; - mDetailDoneButton.setOnClickListener(doneListener); } /** */ @@ -169,6 +160,7 @@ public class QSDetail extends LinearLayout { public void handleShowingDetail(final DetailAdapter adapter, int x, int y, boolean toggleQs) { final boolean showingDetail = adapter != null; + final boolean wasShowingDetail = mDetailAdapter != null; setClickable(showingDetail); if (showingDetail) { setupDetailHeader(adapter); @@ -178,6 +170,7 @@ public class QSDetail extends LinearLayout { } else { mTriggeredExpand = false; } + mShouldAnimate = adapter.shouldAnimate(); mOpenX = x; mOpenY = y; } else { @@ -190,10 +183,10 @@ public class QSDetail extends LinearLayout { } } - boolean visibleDiff = (mDetailAdapter != null) != (adapter != null); - if (!visibleDiff && mDetailAdapter == adapter) return; // already in right state - AnimatorListener listener = null; - if (adapter != null) { + boolean visibleDiff = wasShowingDetail != showingDetail; + if (!visibleDiff && !wasShowingDetail) return; // already in right state + AnimatorListener listener; + if (showingDetail) { int viewCacheIndex = adapter.getMetricsCategory(); View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex), mDetailContent); @@ -213,7 +206,7 @@ public class QSDetail extends LinearLayout { listener = mHideGridContentWhenDone; setVisibility(View.VISIBLE); } else { - if (mDetailAdapter != null) { + if (wasShowingDetail) { Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory()); mUiEventLogger.log(mDetailAdapter.closeDetailEvent()); } @@ -227,7 +220,15 @@ public class QSDetail extends LinearLayout { } sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - animateDetailVisibleDiff(x, y, visibleDiff, listener); + if (mShouldAnimate) { + animateDetailVisibleDiff(x, y, visibleDiff, listener); + } else { + if (showingDetail) { + showImmediately(); + } else { + hideImmediately(); + } + } } protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) { @@ -245,6 +246,17 @@ public class QSDetail extends LinearLayout { } } + void showImmediately() { + setVisibility(VISIBLE); + mClipper.cancelAnimator(); + mClipper.showBackground(); + } + + public void hideImmediately() { + mClipper.cancelAnimator(); + setVisibility(View.GONE); + } + protected void setupDetailFooter(DetailAdapter adapter) { final Intent settingsIntent = adapter.getSettingsIntent(); mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE); @@ -255,6 +267,13 @@ public class QSDetail extends LinearLayout { Dependency.get(ActivityStarter.class) .postStartActivityDismissingKeyguard(settingsIntent, 0); }); + mDetailDoneButton.setOnClickListener(v -> { + announceForAccessibility( + mContext.getString(R.string.accessibility_desc_quick_settings)); + if (!adapter.onDoneButtonClicked()) { + mQsPanelController.closeDetail(); + } + }); } protected void setupDetailHeader(final DetailAdapter adapter) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java index 7d87e174d95d..b50af004aff9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java @@ -39,7 +39,7 @@ public class QSDetailDisplayer { /** Show the supplied DetailAdapter in the Quick Settings. */ public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) { if (mQsPanelController != null) { - mQsPanelController.showDetailDapater(detailAdapter, x, y); + mQsPanelController.showDetailAdapter(detailAdapter, x, y); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 782092161418..fcb35e2040ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -311,7 +311,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } /** */ - public void showDetailDapater(DetailAdapter detailAdapter, int x, int y) { + public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) { mView.showDetailAdapter(true, detailAdapter, new int[]{x, y}); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java index eddcf8c1e9ae..ae0b5d11db13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java @@ -39,6 +39,7 @@ public class QSCarrier extends LinearLayout { private ImageView mMobileSignal; private ImageView mMobileRoaming; private CellSignalState mLastSignalState; + private boolean mProviderModel; public QSCarrier(Context context) { super(context); @@ -59,15 +60,20 @@ public class QSCarrier extends LinearLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mMobileGroup = findViewById(R.id.mobile_combo); if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - mMobileRoaming = findViewById(R.id.mobile_roaming_large); + mProviderModel = true; } else { - mMobileRoaming = findViewById(R.id.mobile_roaming); + mProviderModel = false; } + mMobileGroup = findViewById(R.id.mobile_combo); + mMobileRoaming = findViewById(R.id.mobile_roaming); mMobileSignal = findViewById(R.id.mobile_signal); mCarrierText = findViewById(R.id.qs_carrier_text); - mMobileSignal.setImageDrawable(new SignalDrawable(mContext)); + if (mProviderModel) { + mMobileSignal.setImageDrawable(mContext.getDrawable(R.drawable.ic_qs_no_calling_sms)); + } else { + mMobileSignal.setImageDrawable(new SignalDrawable(mContext)); + } } /** @@ -85,22 +91,27 @@ public class QSCarrier extends LinearLayout { android.R.attr.textColorPrimary); mMobileRoaming.setImageTintList(colorStateList); mMobileSignal.setImageTintList(colorStateList); - mMobileSignal.setImageLevel(state.mobileSignalIconId); - StringBuilder contentDescription = new StringBuilder(); - if (state.contentDescription != null) { - contentDescription.append(state.contentDescription).append(", "); - } - if (state.roaming) { - contentDescription - .append(mContext.getString(R.string.data_connection_roaming)) - .append(", "); - } - // TODO: show mobile data off/no internet text for 5 seconds before carrier text - if (hasValidTypeContentDescription(state.typeContentDescription)) { - contentDescription.append(state.typeContentDescription); + if (mProviderModel) { + mMobileSignal.setImageDrawable(mContext.getDrawable(state.mobileSignalIconId)); + mMobileSignal.setContentDescription(state.contentDescription); + } else { + mMobileSignal.setImageLevel(state.mobileSignalIconId); + StringBuilder contentDescription = new StringBuilder(); + if (state.contentDescription != null) { + contentDescription.append(state.contentDescription).append(", "); + } + if (state.roaming) { + contentDescription + .append(mContext.getString(R.string.data_connection_roaming)) + .append(", "); + } + // TODO: show mobile data off/no internet text for 5 seconds before carrier text + if (hasValidTypeContentDescription(state.typeContentDescription)) { + contentDescription.append(state.typeContentDescription); + } + mMobileSignal.setContentDescription(contentDescription); } - mMobileSignal.setContentDescription(contentDescription); } return true; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java index 77200ccaf5cb..a567f512b204 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.carrier; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import android.annotation.MainThread; +import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; @@ -26,6 +27,7 @@ import android.os.Message; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -33,6 +35,9 @@ import android.widget.TextView; import androidx.annotation.VisibleForTesting; import com.android.keyguard.CarrierTextController; +import com.android.settingslib.AccessibilityContentDescriptions; +import com.android.settingslib.mobile.TelephonyIcons; +import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -62,6 +67,9 @@ public class QSCarrierGroupController { new CellSignalState[SIM_SLOTS]; private View[] mCarrierDividers = new View[SIM_SLOTS - 1]; private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS]; + private int[] mLastSignalLevel = new int[SIM_SLOTS]; + private String[] mLastSignalLevelDescription = new String[SIM_SLOTS]; + private final boolean mProviderModel; private final NetworkController.SignalCallback mSignalCallback = new NetworkController.SignalCallback() { @@ -72,6 +80,9 @@ public class QSCarrierGroupController { CharSequence typeContentDescription, CharSequence typeContentDescriptionHtml, CharSequence description, boolean isWide, int subId, boolean roaming, boolean showTriangle) { + if (mProviderModel) { + return; + } int slotIndex = getSlotIndex(subId); if (slotIndex >= SIM_SLOTS) { Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex); @@ -92,6 +103,46 @@ public class QSCarrierGroupController { } @Override + public void setCallIndicator(NetworkController.IconState statusIcon, int subId) { + if (!mProviderModel) { + return; + } + int slotIndex = getSlotIndex(subId); + if (slotIndex >= SIM_SLOTS) { + Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex); + return; + } + if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + Log.e(TAG, "Invalid SIM slot index for subscription: " + subId); + return; + } + if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) { + if (statusIcon.visible) { + mInfos[slotIndex] = new CellSignalState(true, + statusIcon.icon, statusIcon.contentDescription, "", false); + } else { + // Whenever the no Calling & SMS state is cleared, switched to the last + // known call strength icon. + mInfos[slotIndex] = new CellSignalState( + true, mLastSignalLevel[slotIndex], + mLastSignalLevelDescription[slotIndex], "", false); + } + mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); + } else { + mLastSignalLevel[slotIndex] = statusIcon.icon; + mLastSignalLevelDescription[slotIndex] = statusIcon.contentDescription; + // Only Shows the call strength icon when the no Calling & SMS icon is not + // shown. + if (mInfos[slotIndex].mobileSignalIconId + != R.drawable.ic_qs_no_calling_sms) { + mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon, + statusIcon.contentDescription, "", false); + mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget(); + } + } + } + + @Override public void setNoSims(boolean hasNoSims, boolean simDetected) { if (hasNoSims) { for (int i = 0; i < SIM_SLOTS; i++) { @@ -118,7 +169,12 @@ public class QSCarrierGroupController { private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter, @Background Handler bgHandler, @Main Looper mainLooper, NetworkController networkController, - CarrierTextController.Builder carrierTextControllerBuilder) { + CarrierTextController.Builder carrierTextControllerBuilder, Context context) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + mProviderModel = true; + } else { + mProviderModel = false; + } mActivityStarter = activityStarter; mBgHandler = bgHandler; mNetworkController = networkController; @@ -149,7 +205,13 @@ public class QSCarrierGroupController { mCarrierDividers[1] = view.getCarrierDivider2(); for (int i = 0; i < SIM_SLOTS; i++) { - mInfos[i] = new CellSignalState(); + mInfos[i] = new CellSignalState(true, R.drawable.ic_qs_no_calling_sms, + context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(), + "", false); + mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0]; + mLastSignalLevelDescription[i] = + context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]) + .toString(); mCarrierGroups[i].setOnClickListener(onClickListener); } view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); @@ -305,16 +367,18 @@ public class QSCarrierGroupController { private final Looper mLooper; private final NetworkController mNetworkController; private final CarrierTextController.Builder mCarrierTextControllerBuilder; + private final Context mContext; @Inject public Builder(ActivityStarter activityStarter, @Background Handler handler, @Main Looper looper, NetworkController networkController, - CarrierTextController.Builder carrierTextControllerBuilder) { + CarrierTextController.Builder carrierTextControllerBuilder, Context context) { mActivityStarter = activityStarter; mHandler = handler; mLooper = looper; mNetworkController = networkController; mCarrierTextControllerBuilder = carrierTextControllerBuilder; + mContext = context; } public Builder setQSCarrierGroup(QSCarrierGroup view) { @@ -324,7 +388,7 @@ public class QSCarrierGroupController { public QSCarrierGroupController build() { return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper, - mNetworkController, mCarrierTextControllerBuilder); + mNetworkController, mCarrierTextControllerBuilder, mContext); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index abf230e31e93..d4bab2197249 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -102,6 +102,12 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { public void onConfigChanged(Configuration newConfig) { mView.updateNavBackDrop(newConfig, mLightBarController); mView.updateResources(); + if (mTileAdapter.updateNumColumns()) { + RecyclerView.LayoutManager lm = mView.getRecyclerView().getLayoutManager(); + if (lm instanceof GridLayoutManager) { + ((GridLayoutManager) lm).setSpanCount(mTileAdapter.getNumColumns()); + } + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 21464fd37c6c..048fdc3a0e5a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -98,7 +98,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private final UiEventLogger mUiEventLogger; private final AccessibilityDelegateCompat mAccessibilityDelegate; private RecyclerView mRecyclerView; - private final int mNumColumns; + private int mNumColumns; @Inject public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) { @@ -123,6 +123,21 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mRecyclerView = null; } + /** + * Update the number of columns to show, from resources. + * + * @return {@code true} if the number of columns changed, {@code false} otherwise + */ + public boolean updateNumColumns() { + int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns); + if (numColumns != mNumColumns) { + mNumColumns = numColumns; + return true; + } else { + return false; + } + } + public int getNumColumns() { return mNumColumns; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 191b85bd1f21..0abff77c4e39 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -469,9 +469,8 @@ public class InternetTile extends QSTileImpl<SignalState> { if (wifiConnected) { minimalStateDescription.append(cb.mWifiSignalContentDescription); minimalContentDescription.append(removeDoubleQuotes(cb.mSsid)); - if (!TextUtils.isEmpty(state.secondaryLabel)) { - minimalContentDescription.append(",").append(state.secondaryLabel); - } + } else if (!TextUtils.isEmpty(state.secondaryLabel)) { + minimalContentDescription.append(",").append(state.secondaryLabel); } } state.stateDescription = minimalStateDescription.toString(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java index 6a8c61491709..6ca550c9ddb2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java @@ -41,7 +41,7 @@ public class UserDetailItemView extends LinearLayout { protected static int layoutResId = R.layout.qs_user_detail_item; private UserAvatarView mAvatar; - private TextView mName; + protected TextView mName; private int mActivatedStyle; private int mRegularStyle; private View mRestrictedPadlock; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java index 26adfdcd8539..a6cddd3367d3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java @@ -82,7 +82,7 @@ public class UserTile extends QSTileImpl<State> implements UserInfoController.On @Override public DetailAdapter getDetailAdapter() { - return mUserSwitcherController.userDetailAdapter; + return mUserSwitcherController.mUserDetailAdapter; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index c8afd0b6cfe9..9383aefeb6b6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -16,17 +16,22 @@ package com.android.systemui.screenshot; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import com.android.systemui.R; @@ -35,6 +40,7 @@ import com.android.systemui.R; * cropped out. */ public class CropView extends View { + private static final String TAG = "CropView"; public enum CropBoundary { NONE, TOP, BOTTOM } @@ -118,10 +124,7 @@ public class CropView extends View { case MotionEvent.ACTION_UP: if (mCurrentDraggingBoundary != CropBoundary.NONE) { // Commit the delta to the stored crop values. - mTopCrop += mTopDelta; - mBottomCrop += mBottomDelta; - mTopDelta = 0; - mBottomDelta = 0; + commitDeltas(); updateListener(event); } } @@ -129,6 +132,42 @@ public class CropView extends View { } /** + * Animate the given boundary to the given value. + */ + public void animateBoundaryTo(CropBoundary boundary, float value) { + if (boundary == CropBoundary.NONE) { + Log.w(TAG, "No boundary selected for animation"); + return; + } + float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop) + : (value - mBottomCrop); + ValueAnimator animator = new ValueAnimator(); + animator.addUpdateListener(animation -> { + if (boundary == CropBoundary.TOP) { + mTopDelta = animation.getAnimatedFraction() * totalDelta; + } else { + mBottomDelta = animation.getAnimatedFraction() * totalDelta; + } + invalidate(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + commitDeltas(); + } + + @Override + public void onAnimationCancel(Animator animation) { + commitDeltas(); + } + }); + animator.setFloatValues(0f, 1f); + animator.setDuration(750); + animator.setInterpolator(new FastOutSlowInInterpolator()); + animator.start(); + } + + /** * @return value [0,1] representing the position of the top crop boundary. Does not reflect * changes from any in-progress touch input. */ @@ -148,6 +187,13 @@ public class CropView extends View { mCropInteractionListener = listener; } + private void commitDeltas() { + mTopCrop += mTopDelta; + mBottomCrop += mBottomDelta; + mTopDelta = 0; + mBottomDelta = 0; + } + private void updateListener(MotionEvent event) { if (mCropInteractionListener != null) { float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java index 212e6c86e9da..a95c91bfeceb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java @@ -70,7 +70,7 @@ class ImageTile implements AutoCloseable { RecordingCanvas canvas = mNode.beginRecording(w, h); canvas.save(); - canvas.clipRect(0, 0, mLocation.right, mLocation.bottom); + canvas.clipRect(0, 0, mLocation.width(), mLocation.height()); canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE), 0, 0, null); canvas.restore(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java index 20f845103723..ae3cd9996f04 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java @@ -21,6 +21,8 @@ import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.Log; import androidx.annotation.UiThread; @@ -32,11 +34,14 @@ import java.util.List; * <p> * To display on-screen, use {@link #getDrawable()}. */ -@UiThread class ImageTileSet { private static final String TAG = "ImageTileSet"; + ImageTileSet(@UiThread Handler handler) { + mHandler = handler; + } + interface OnBoundsChangedListener { /** * Reports an update to the bounding box that contains all active tiles. These are virtual @@ -54,6 +59,7 @@ class ImageTileSet { private final List<ImageTile> mTiles = new ArrayList<>(); private final Rect mBounds = new Rect(); + private final Handler mHandler; private OnContentChangedListener mOnContentChangedListener; private OnBoundsChangedListener mOnBoundsChangedListener; @@ -73,13 +79,32 @@ class ImageTileSet { newBounds.union(newRect); if (!newBounds.equals(mBounds)) { mBounds.set(newBounds); - if (mOnBoundsChangedListener != null) { - mOnBoundsChangedListener.onBoundsChanged( - newBounds.left, newBounds.top, newBounds.right, newBounds.bottom); - } + notifyBoundsChanged(mBounds); } - if (mOnContentChangedListener != null) { + notifyContentChanged(); + } + + void notifyContentChanged() { + if (mOnContentChangedListener == null) { + return; + } + if (mHandler.getLooper().isCurrentThread()) { mOnContentChangedListener.onContentChanged(); + } else { + mHandler.post(() -> mOnContentChangedListener.onContentChanged()); + } + } + + void notifyBoundsChanged(Rect bounds) { + if (mOnBoundsChangedListener == null) { + return; + } + if (mHandler.getLooper().isCurrentThread()) { + mOnBoundsChangedListener.onBoundsChanged( + bounds.left, bounds.top, bounds.right, bounds.bottom); + } else { + mHandler.post(() -> mOnBoundsChangedListener.onBoundsChanged( + bounds.left, bounds.top, bounds.right, bounds.bottom)); } } @@ -117,22 +142,16 @@ class ImageTileSet { * getHeight()). */ Bitmap toBitmap(Rect bounds) { + Log.d(TAG, "exporting with bounds: " + bounds); if (mTiles.isEmpty()) { return null; } final RenderNode output = new RenderNode("Bitmap Export"); - output.setPosition(0, 0, getWidth(), getHeight()); + output.setPosition(0, 0, bounds.width(), bounds.height()); RecordingCanvas canvas = output.beginRecording(); - canvas.translate(-getLeft(), -getTop()); - // Additional translation to account for the requested bounds - canvas.translate(-bounds.left, -bounds.top); - canvas.clipRect(bounds); - for (ImageTile tile : mTiles) { - canvas.save(); - canvas.translate(tile.getLeft(), tile.getTop()); - canvas.drawRenderNode(tile.getDisplayList()); - canvas.restore(); - } + Drawable drawable = getDrawable(); + drawable.setBounds(bounds); + drawable.draw(canvas); output.endRecording(); return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height()); } @@ -162,14 +181,13 @@ class ImageTileSet { } void clear() { - mBounds.set(0, 0, 0, 0); + if (mBounds.isEmpty()) { + return; + } + mBounds.setEmpty(); mTiles.forEach(ImageTile::close); mTiles.clear(); - if (mOnBoundsChangedListener != null) { - mOnBoundsChangedListener.onBoundsChanged(0, 0, 0, 0); - } - if (mOnContentChangedListener != null) { - mOnContentChangedListener.onContentChanged(); - } + notifyBoundsChanged(mBounds); + notifyContentChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java index f88715164bc7..f8f1d3ac9a5b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java @@ -144,7 +144,9 @@ public class MagnifierView extends View implements CropView.CropInteractionListe setAlpha(0f); setTranslationX((getParentWidth() - getWidth()) / 2); setVisibility(View.VISIBLE); - animate().alpha(1f).translationX(0).scaleX(1f).scaleY(1f).start(); + boolean touchOnRight = event.getX() > getParentWidth() / 2; + float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); + animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f).start(); break; case MotionEvent.ACTION_MOVE: mLastCropPosition = cropPosition; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java index bb07012f2355..d56c806554d4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java @@ -50,8 +50,6 @@ import javax.inject.Inject; public class ScrollCaptureClient { private static final int TILE_SIZE_PX_MAX = 4 * (1024 * 1024); private static final int TILES_PER_PAGE = 2; // increase once b/174571735 is addressed - private static final int MAX_PAGES = 5; - private static final int MAX_IMAGE_COUNT = MAX_PAGES * TILES_PER_PAGE; @VisibleForTesting static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID; @@ -66,10 +64,11 @@ public class ScrollCaptureClient { /** * Session start should be deferred until UI is active because of resource allocation and * potential visible side effects in the target window. - + * * @param sessionConsumer listener to receive the session once active + * @param maxPages the capture buffer size expressed as a multiple of the content height */ - void start(Consumer<Session> sessionConsumer); + void start(Consumer<Session> sessionConsumer, float maxPages); /** * Close the connection. @@ -196,6 +195,7 @@ public class ScrollCaptureClient { private int mTileWidth; private Rect mRequestRect; private boolean mStarted; + private int mMaxTiles; private ControllerCallbacks(Consumer<Connection> connectionConsumer) { mConnectionConsumer = connectionConsumer; @@ -285,12 +285,15 @@ public class ScrollCaptureClient { // ScrollCaptureController.Connection @Override - public void start(Consumer<Session> sessionConsumer) { + public void start(Consumer<Session> sessionConsumer, float maxPages) { if (DEBUG_SCROLL) { - Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ")"); + Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + "," + + " maxPages=" + maxPages + ")" + + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]"); } + mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE); mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, - MAX_IMAGE_COUNT, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); + mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); mSessionConsumer = sessionConsumer; try { mConnection.startCapture(mReader.getSurface()); @@ -345,7 +348,7 @@ public class ScrollCaptureClient { @Override public int getMaxTiles() { - return MAX_IMAGE_COUNT; + return mMaxTiles; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 25438a6f57ba..d97f644c5d23 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.graphics.Rect; import android.net.Uri; import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -34,6 +35,7 @@ import android.widget.ImageView; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; import com.android.systemui.screenshot.ScrollCaptureClient.Connection; import com.android.systemui.screenshot.ScrollCaptureClient.Session; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; @@ -44,13 +46,23 @@ import java.time.ZonedDateTime; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * Interaction controller between the UI and ScrollCaptureClient. */ public class ScrollCaptureController implements OnComputeInternalInsetsListener { private static final String TAG = "ScrollCaptureController"; + private static final float MAX_PAGES_DEFAULT = 3f; + + private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages"; + + private static final int UP = -1; + private static final int DOWN = 1; + + private int mDirection = DOWN; + private boolean mAtBottomEdge; + private boolean mAtTopEdge; + private Session mSession; // TODO: Support saving without additional action. private enum PendingAction { @@ -59,7 +71,6 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener SAVE } - public static final int MAX_PAGES = 5; public static final int MAX_HEIGHT = 12000; private final Connection mConnection; @@ -91,7 +102,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener mBgExecutor = bgExecutor; mImageExporter = exporter; mUiEventLogger = uiEventLogger; - mImageTileSet = new ImageTileSet(); + mImageTileSet = new ImageTileSet(context.getMainThreadHandler()); } /** @@ -129,7 +140,9 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener mEdit.setOnClickListener(this::onClicked); mShare.setOnClickListener(this::onClicked); - mConnection.start(this::startCapture); + float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(), + SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT); + mConnection.start(this::startCapture, maxPages); } @@ -232,41 +245,82 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener return mWindow.findViewById(res); } + + private void onCaptureResult(CaptureResult result) { + Log.d(TAG, "onCaptureResult: " + result); + boolean emptyResult = result.captured.height() == 0; + boolean partialResult = !emptyResult + && result.captured.height() < result.requested.height(); + boolean finish = false; + + if (partialResult) { + // Potentially reached a vertical boundary. Extend in the other direction. + switch (mDirection) { + case DOWN: + Log.d(TAG, "Reached bottom edge."); + mAtBottomEdge = true; + mDirection = UP; + break; + case UP: + Log.d(TAG, "Reached top edge."); + mAtTopEdge = true; + mDirection = DOWN; + break; + } + + if (mAtTopEdge && mAtBottomEdge) { + Log.d(TAG, "Reached both top and bottom edge, ending."); + finish = true; + } else { + // only reverse if the edge was relatively close to the starting point + if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) { + Log.d(TAG, "Restarting in reverse direction."); + + // Because of temporary limitations, we cannot just jump to the opposite edge + // and continue there. Instead, clear the results and start over capturing from + // here in the other direction. + mImageTileSet.clear(); + } else { + Log.d(TAG, "Capture is tall enough, stopping here."); + finish = true; + } + } + } + + if (!emptyResult) { + mImageTileSet.addTile(new ImageTile(result.image, result.captured)); + } + + Log.d(TAG, "bounds: " + mImageTileSet.getLeft() + "," + mImageTileSet.getTop() + + " - " + mImageTileSet.getRight() + "," + mImageTileSet.getBottom() + + " (" + mImageTileSet.getWidth() + "x" + mImageTileSet.getHeight() + ")"); + + + // Stop when "too tall" + if (mImageTileSet.size() >= mSession.getMaxTiles() + || mImageTileSet.getHeight() > MAX_HEIGHT) { + Log.d(TAG, "Max height and/or tile count reached."); + finish = true; + } + + if (finish) { + Session session = mSession; + mSession = null; + Log.d(TAG, "Stop."); + mUiExecutor.execute(() -> afterCaptureComplete(session)); + return; + } + + int nextTop = (mDirection == DOWN) ? result.captured.bottom + : result.captured.top - mSession.getTileHeight(); + Log.d(TAG, "requestTile: " + nextTop); + mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult); + } + private void startCapture(Session session) { - Log.d(TAG, "startCapture"); - Consumer<ScrollCaptureClient.CaptureResult> consumer = - new Consumer<ScrollCaptureClient.CaptureResult>() { - - int mFrameCount = 0; - int mTop = 0; - - @Override - public void accept(ScrollCaptureClient.CaptureResult result) { - mFrameCount++; - - boolean emptyFrame = result.captured.height() == 0; - if (!emptyFrame) { - ImageTile tile = new ImageTile(result.image, result.captured); - Log.d(TAG, "Adding tile: " + tile); - mImageTileSet.addTile(tile); - Log.d(TAG, "New dimens: w=" + mImageTileSet.getWidth() + ", " - + "h=" + mImageTileSet.getHeight()); - } - - if (emptyFrame || mFrameCount >= MAX_PAGES - || mTop + session.getTileHeight() > MAX_HEIGHT) { - - mUiExecutor.execute(() -> afterCaptureComplete(session)); - return; - } - mTop += result.captured.height(); - session.requestTile(mTop, /* consumer */ this); - } - }; - - // fire it up! - session.requestTile(0, consumer); - }; + mSession = session; + session.requestTile(0, this::onCaptureResult); + } @UiThread void afterCaptureComplete(Session session) { @@ -277,6 +331,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener } else { mPreview.setImageDrawable(mImageTileSet.getDrawable()); mMagnifierView.setImageTileset(mImageTileSet); + mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f); } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java index 72f489bdd398..4ec8eb22c67a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java @@ -56,7 +56,7 @@ public class TiledImageDrawable extends Drawable { mNode = new RenderNode("TiledImageDrawable"); } mNode.setPosition(0, 0, mTiles.getWidth(), mTiles.getHeight()); - Canvas canvas = mNode.beginRecording(mTiles.getWidth(), mTiles.getHeight()); + Canvas canvas = mNode.beginRecording(); // Align content (virtual) top/left with 0,0, within the render node canvas.translate(-mTiles.getLeft(), -mTiles.getTop()); for (int i = 0; i < mTiles.size(); i++) { @@ -79,8 +79,8 @@ public class TiledImageDrawable extends Drawable { if (canvas.isHardwareAccelerated()) { Rect bounds = getBounds(); canvas.save(); - canvas.clipRect(bounds); - canvas.translate(bounds.left, bounds.top); + canvas.clipRect(0, 0, bounds.width(), bounds.height()); + canvas.translate(-bounds.left, -bounds.top); canvas.drawRenderNode(mNode); canvas.restore(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 778f813b8d07..862c27907e0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -78,4 +78,8 @@ public class FeatureFlags { public boolean isToastStyleEnabled() { return mFlagReader.isEnabled(R.bool.flag_toast_style); } + + public boolean isMonetEnabled() { + return mFlagReader.isEnabled(R.bool.flag_monet); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java index 0465ebff9aeb..edcf6d4eddb3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java @@ -18,23 +18,19 @@ package com.android.systemui.statusbar.notification; import static android.service.notification.NotificationListenerService.Ranking; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK; + import android.app.NotificationManager; -import android.content.ContentResolver; import android.content.Context; -import android.database.ContentObserver; -import android.net.Uri; import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.util.Pair; -import androidx.annotation.Nullable; - import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.util.DeviceConfigProxy; import javax.inject.Inject; @@ -45,10 +41,10 @@ import javax.inject.Inject; * should show an indicator. */ @SysUISingleton -public class AssistantFeedbackController extends ContentObserver { - private final Uri FEEDBACK_URI - = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED); - private ContentResolver mResolver; +public class AssistantFeedbackController { + private final Context mContext; + private final Handler mHandler; + private final DeviceConfigProxy mDeviceConfigProxy; public static final int STATUS_UNCHANGED = 0; public static final int STATUS_ALERTED = 1; @@ -56,34 +52,39 @@ public class AssistantFeedbackController extends ContentObserver { public static final int STATUS_PROMOTED = 3; public static final int STATUS_DEMOTED = 4; - private boolean mFeedbackEnabled; + private volatile boolean mFeedbackEnabled; + + private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(ENABLE_NAS_FEEDBACK)) { + mFeedbackEnabled = properties.getBoolean( + ENABLE_NAS_FEEDBACK, false); + } + } + }; /** Injected constructor */ @Inject - public AssistantFeedbackController(Context context) { - super(new Handler(Looper.getMainLooper())); - mResolver = context.getContentResolver(); - mResolver.registerContentObserver(FEEDBACK_URI, false, this, UserHandle.USER_ALL); - update(null); + public AssistantFeedbackController(@Main Handler handler, + Context context, DeviceConfigProxy proxy) { + mHandler = handler; + mContext = context; + mDeviceConfigProxy = proxy; + mFeedbackEnabled = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + ENABLE_NAS_FEEDBACK, false); + mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, + this::postToHandler, mPropertiesChangedListener); } - @Override - public void onChange(boolean selfChange, @Nullable Uri uri, int flags) { - update(uri); - } - - @VisibleForTesting - public void update(@Nullable Uri uri) { - if (uri == null || FEEDBACK_URI.equals(uri)) { - mFeedbackEnabled = Settings.Global.getInt(mResolver, - Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, 0) - != 0; - } + private void postToHandler(Runnable r) { + this.mHandler.post(r); } /** - * Determines whether to show any user controls related to the assistant. This is based on the - * settings flag {@link Settings.Global.NOTIFICATION_FEEDBACK_ENABLED} + * Determines whether to show any user controls related to the assistant based on the + * DeviceConfig flag value */ public boolean isFeedbackEnabled() { return mFeedbackEnabled; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 57a64e440bf6..a6daed5a0850 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -27,6 +27,7 @@ import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView; /** * Utility class to calculate the clock position and top padding of notifications on Keyguard. @@ -55,11 +56,24 @@ public class KeyguardClockPositionAlgorithm { private int mKeyguardStatusHeight; /** + * Height of user avatar used by the multi-user switcher. This could either be the + * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is + * visible, or it could be height of the avatar used by the + * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}. + */ + private int mUserSwitchHeight; + + /** * Preferred Y position of clock. */ private int mClockPreferredY; /** + * Preferred Y position of user avatar used by the multi-user switcher. + */ + private int mUserSwitchPreferredY; + + /** * Whether or not there is a custom clock face on keyguard. */ private boolean mHasCustomClock; @@ -173,18 +187,22 @@ public class KeyguardClockPositionAlgorithm { * Sets up algorithm values. */ public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight, - float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY, + float panelExpansion, int parentHeight, int keyguardStatusHeight, + int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY, boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion, int cutoutTopInset) { mMinTopMargin = statusBarMinHeight + (showLockIcon - ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon); + ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon) + + userSwitchHeight; mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; mPanelExpansion = panelExpansion; mHeight = parentHeight; mKeyguardStatusHeight = keyguardStatusHeight; + mUserSwitchHeight = userSwitchHeight; mClockPreferredY = clockPreferredY; + mUserSwitchPreferredY = userSwitchPreferredY; mHasCustomClock = hasCustomClock; mHasVisibleNotifs = hasVisibleNotifs; mDarkAmount = dark; @@ -198,6 +216,7 @@ public class KeyguardClockPositionAlgorithm { public void run(Result result) { final int y = getClockY(mPanelExpansion, mDarkAmount); result.clockY = y; + result.userSwitchY = getUserSwitcherY(mPanelExpansion); result.clockYFullyDozing = getClockY( 1.0f /* panelExpansion */, 1.0f /* darkAmount */); result.clockAlpha = getClockAlpha(y); @@ -231,7 +250,7 @@ public class KeyguardClockPositionAlgorithm { private int getExpandedPreferredClockY() { if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) { - return mMinTopMargin; + return mMinTopMargin + mUserSwitchHeight; } return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY() : getExpandedClockPosition(); @@ -246,7 +265,8 @@ public class KeyguardClockPositionAlgorithm { final int availableHeight = mMaxShadeBottom - mMinTopMargin; final int containerCenter = mMinTopMargin + availableHeight / 2; - float y = containerCenter - mKeyguardStatusHeight * CLOCK_HEIGHT_WEIGHT + float y = containerCenter + - (mKeyguardStatusHeight + mUserSwitchHeight) * CLOCK_HEIGHT_WEIGHT - mClockNotificationsMargin - mNotificationStackHeight / 2; if (y < mMinTopMargin) { y = mMinTopMargin; @@ -288,6 +308,17 @@ public class KeyguardClockPositionAlgorithm { return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount); } + private int getUserSwitcherY(float panelExpansion) { + float userSwitchYRegular = mUserSwitchPreferredY; + float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight; + + // Move user-switch up while collapsing the shade + float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion); + float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion); + + return (int) (userSwitchY + mEmptyDragAmount); + } + /** * We might want to fade out the clock when the user is swiping up. * One exception is when the bouncer will become visible, in this cause the clock @@ -330,6 +361,11 @@ public class KeyguardClockPositionAlgorithm { public int clockY; /** + * The y translation of the multi-user switch. + */ + public int userSwitchY; + + /** * The y translation of the clock when we're fully dozing. */ public int clockYFullyDozing; 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 5f547b5df671..33798d680d05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; import android.annotation.ColorInt; @@ -25,6 +26,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.UserManager; import android.util.AttributeSet; import android.util.Pair; import android.util.TypedValue; @@ -45,18 +47,15 @@ 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.qs.QSDetailDisplayer; 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; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; -import com.android.systemui.statusbar.policy.UserSwitcherController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -75,18 +74,16 @@ public class KeyguardStatusBarView extends RelativeLayout private boolean mShowPercentAvailable; private boolean mBatteryCharging; - private boolean mKeyguardUserSwitcherShowing; private boolean mBatteryListening; private TextView mCarrierLabel; - private MultiUserSwitch mMultiUserSwitch; private ImageView mMultiUserAvatar; private BatteryMeterView mBatteryView; private StatusIconContainer mStatusIconContainer; private BatteryController mBatteryController; - private KeyguardUserSwitcher mKeyguardUserSwitcher; - private UserSwitcherController mUserSwitcherController; + private boolean mKeyguardUserSwitcherEnabled; + private final UserManager mUserManager; private int mSystemIconsSwitcherHiddenExpandedMargin; private int mSystemIconsBaseMargin; @@ -109,13 +106,13 @@ public class KeyguardStatusBarView extends RelativeLayout public KeyguardStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); + mUserManager = UserManager.get(getContext()); } @Override protected void onFinishInflate() { super.onFinishInflate(); mSystemIconsContainer = findViewById(R.id.system_icons_container); - mMultiUserSwitch = findViewById(R.id.multi_user_switch); mMultiUserAvatar = findViewById(R.id.multi_user_avatar); mCarrierLabel = findViewById(R.id.keyguard_carrier_text); mBatteryView = mSystemIconsContainer.findViewById(R.id.battery); @@ -124,7 +121,6 @@ public class KeyguardStatusBarView extends RelativeLayout mStatusIconContainer = findViewById(R.id.statusIcons); loadDimens(); - updateUserSwitcher(); mBatteryController = Dependency.get(BatteryController.class); } @@ -137,14 +133,6 @@ public class KeyguardStatusBarView extends RelativeLayout R.dimen.multi_user_avatar_keyguard_size); mMultiUserAvatar.setLayoutParams(lp); - // Multi-user switch - lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams(); - lp.width = getResources().getDimensionPixelSize( - R.dimen.multi_user_switch_width_keyguard); - lp.setMarginEnd(getResources().getDimensionPixelSize( - R.dimen.multi_user_switch_keyguard_margin)); - mMultiUserSwitch.setLayoutParams(lp); - // System icons lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams(); lp.setMarginStart(getResources().getDimensionPixelSize( @@ -194,22 +182,28 @@ public class KeyguardStatusBarView extends RelativeLayout } private void updateVisibilities() { - if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) { - if (mMultiUserSwitch.getParent() != null) { - getOverlay().remove(mMultiUserSwitch); + if (mMultiUserAvatar.getParent() != mStatusIconArea + && !mKeyguardUserSwitcherEnabled) { + if (mMultiUserAvatar.getParent() != null) { + getOverlay().remove(mMultiUserAvatar); } - mStatusIconArea.addView(mMultiUserSwitch, 0); - } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) { - mStatusIconArea.removeView(mMultiUserSwitch); + mStatusIconArea.addView(mMultiUserAvatar, 0); + } else if (mMultiUserAvatar.getParent() == mStatusIconArea + && mKeyguardUserSwitcherEnabled) { + mStatusIconArea.removeView(mMultiUserAvatar); } - if (mKeyguardUserSwitcher == null) { + if (!mKeyguardUserSwitcherEnabled) { // If we have no keyguard switcher, the screen width is under 600dp. In this case, // we only show the multi-user switch if it's enabled through UserManager as well as // by the user. - if (mMultiUserSwitch.isMultiUserEnabled()) { - mMultiUserSwitch.setVisibility(View.VISIBLE); + // TODO(b/138661450) Move IPC calls to background + boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled( + mContext.getResources().getBoolean( + R.bool.qs_show_user_switcher_for_single_user))); + if (isMultiUserEnabled) { + mMultiUserAvatar.setVisibility(View.VISIBLE); } else { - mMultiUserSwitch.setVisibility(View.GONE); + mMultiUserAvatar.setVisibility(View.GONE); } } mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable); @@ -220,11 +214,12 @@ public class KeyguardStatusBarView extends RelativeLayout (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); // If the avatar icon is gone, we need to have some end margin to display the system icons // correctly. - int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE + int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE ? mSystemIconsBaseMargin : 0; - int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin : - baseMarginEnd; + int marginEnd = + mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin + : baseMarginEnd; marginEnd = calculateMargin(marginEnd, mPadding.second); if (marginEnd != lp.getMarginEnd()) { lp.setMarginEnd(marginEnd); @@ -334,20 +329,11 @@ public class KeyguardStatusBarView extends RelativeLayout } } - private void updateUserSwitcher() { - boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null; - mMultiUserSwitch.setClickable(keyguardSwitcherAvailable); - mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable); - mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable); - } - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); UserInfoController userInfoController = Dependency.get(UserInfoController.class); userInfoController.addCallback(this); - mUserSwitcherController = Dependency.get(UserSwitcherController.class); - mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController); userInfoController.reloadUserInfo(); Dependency.get(ConfigurationController.class).addCallback(this); mIconManager = new TintedIconManager(findViewById(R.id.statusIcons), @@ -369,11 +355,6 @@ public class KeyguardStatusBarView extends RelativeLayout mMultiUserAvatar.setImageDrawable(picture); } - /** */ - public void setQSDetailDisplayer(QSDetailDisplayer detailDisplayer) { - mMultiUserSwitch.setQSDetailDisplayer(detailDisplayer); - } - @Override public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { if (mBatteryCharging != charging) { @@ -387,54 +368,42 @@ public class KeyguardStatusBarView extends RelativeLayout // could not care less } - public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { - mKeyguardUserSwitcher = keyguardUserSwitcher; - mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher); - updateUserSwitcher(); - } - - public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) { - mKeyguardUserSwitcherShowing = showing; - if (animate) { - animateNextLayoutChange(); - } - updateVisibilities(); - updateLayoutConsideringCutout(); - updateSystemIconsLayoutParams(); + public void setKeyguardUserSwitcherEnabled(boolean enabled) { + mKeyguardUserSwitcherEnabled = enabled; } private void animateNextLayoutChange() { final int systemIconsCurrentX = mSystemIconsContainer.getLeft(); - final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea; + final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea; getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); - boolean userSwitcherHiding = userSwitcherVisible - && mMultiUserSwitch.getParent() != mStatusIconArea; + boolean userAvatarHiding = userAvatarVisible + && mMultiUserAvatar.getParent() != mStatusIconArea; mSystemIconsContainer.setX(systemIconsCurrentX); mSystemIconsContainer.animate() .translationX(0) .setDuration(400) - .setStartDelay(userSwitcherHiding ? 300 : 0) + .setStartDelay(userAvatarHiding ? 300 : 0) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .start(); - if (userSwitcherHiding) { - getOverlay().add(mMultiUserSwitch); - mMultiUserSwitch.animate() + if (userAvatarHiding) { + getOverlay().add(mMultiUserAvatar); + mMultiUserAvatar.animate() .alpha(0f) .setDuration(300) .setStartDelay(0) .setInterpolator(Interpolators.ALPHA_OUT) .withEndAction(() -> { - mMultiUserSwitch.setAlpha(1f); - getOverlay().remove(mMultiUserSwitch); + mMultiUserAvatar.setAlpha(1f); + getOverlay().remove(mMultiUserAvatar); }) .start(); } else { - mMultiUserSwitch.setAlpha(0f); - mMultiUserSwitch.animate() + mMultiUserAvatar.setAlpha(0f); + mMultiUserAvatar.animate() .alpha(1f) .setDuration(300) .setStartDelay(200) @@ -452,8 +421,8 @@ public class KeyguardStatusBarView extends RelativeLayout if (visibility != View.VISIBLE) { mSystemIconsContainer.animate().cancel(); mSystemIconsContainer.setTranslationX(0); - mMultiUserSwitch.animate().cancel(); - mMultiUserSwitch.setAlpha(1f); + mMultiUserAvatar.animate().cancel(); + mMultiUserAvatar.setAlpha(1f); } else { updateVisibilities(); updateSystemIconsLayoutParams(); @@ -523,9 +492,9 @@ public class KeyguardStatusBarView extends RelativeLayout public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("KeyguardStatusBarView:"); pw.println(" mBatteryCharging: " + mBatteryCharging); - pw.println(" mKeyguardUserSwitcherShowing: " + mKeyguardUserSwitcherShowing); pw.println(" mBatteryListening: " + mBatteryListening); pw.println(" mLayoutState: " + mLayoutState); + pw.println(" mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled); if (mBatteryView != null) { mBatteryView.dump(fd, pw, args); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java index 480d3f42ae77..16f36b7b6b7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java @@ -35,7 +35,6 @@ import com.android.systemui.Prefs.Key; import com.android.systemui.R; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.qs.QSDetailDisplayer; -import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.UserSwitcherController; /** @@ -44,8 +43,6 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; public class MultiUserSwitch extends FrameLayout implements View.OnClickListener { protected QSDetailDisplayer mQSDetailDisplayer; - private KeyguardUserSwitcher mKeyguardUserSwitcher; - private boolean mKeyguardMode; private UserSwitcherController.BaseUserAdapter mUserListener; final UserManager mUserManager; @@ -85,15 +82,6 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener refreshContentDescription(); } - public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { - mKeyguardUserSwitcher = keyguardUserSwitcher; - } - - public void setKeyguardMode(boolean keyguardShowing) { - mKeyguardMode = keyguardShowing; - registerListener(); - } - public boolean isMultiUserEnabled() { // TODO(b/138661450) Move IPC calls to background return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled( @@ -123,11 +111,7 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener @Override public void onClick(View v) { - if (mKeyguardMode) { - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.show(true /* animate */); - } - } else if (mQSDetailDisplayer != null && mUserSwitcherController != null) { + if (mQSDetailDisplayer != null && mUserSwitcherController != null) { View center = getChildCount() > 0 ? getChildAt(0) : this; int[] tmpInt = new int[2]; @@ -184,6 +168,6 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener } protected DetailAdapter getUserDetailAdapter() { - return mUserSwitcherController.userDetailAdapter; + return mUserSwitcherController.mUserDetailAdapter; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 7730622a9502..1318c13d7c07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -18,6 +18,10 @@ package com.android.systemui.statusbar.phone; import static android.view.View.GONE; +import static androidx.constraintlayout.widget.ConstraintSet.END; +import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; +import static androidx.constraintlayout.widget.ConstraintSet.START; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; @@ -48,6 +52,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; +import android.os.UserManager; import android.util.Log; import android.util.MathUtils; import android.view.DisplayCutout; @@ -57,6 +62,7 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.ViewPropertyAnimator; +import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.accessibility.AccessibilityManager; @@ -64,6 +70,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.constraintlayout.widget.ConstraintSet; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; @@ -75,7 +83,9 @@ import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; +import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -92,6 +102,7 @@ import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; @@ -131,8 +142,10 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; +import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; +import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.util.Utils; import com.android.wm.shell.animation.FlingAnimationUtils; @@ -284,6 +297,21 @@ public class NotificationPanelViewController extends PanelViewController { } }; + final KeyguardUserSwitcherController.KeyguardUserSwitcherListener + mKeyguardUserSwitcherListener = + new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() { + @Override + public void onKeyguardUserSwitcherChanged(boolean open) { + if (mKeyguardUserSwitcherController == null) { + updateUserSwitcherVisibility(false); + } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) { + updateUserSwitcherVisibility(open + && mKeyguardStateController.isShowing() + && !mKeyguardStateController.isKeyguardFadingAway()); + } + } + }; + private final LayoutInflater mLayoutInflater; private final PowerManager mPowerManager; private final AccessibilityManager mAccessibilityManager; @@ -296,6 +324,8 @@ public class NotificationPanelViewController extends PanelViewController { private final MediaHierarchyManager mMediaHierarchyManager; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; + private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; + private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; private final QSDetailDisplayer mQSDetailDisplayer; private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; @@ -308,7 +338,9 @@ public class NotificationPanelViewController extends PanelViewController { private int mMaxAllowedKeyguardNotifications; private KeyguardAffordanceHelper mAffordanceHelper; - private KeyguardUserSwitcher mKeyguardUserSwitcher; + private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; + private boolean mKeyguardUserSwitcherIsShowing; + private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; private ViewGroup mBigClockContainer; private QS mQs; @@ -333,6 +365,8 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mQsExpandedWhenExpandingStarted; private boolean mQsFullyExpanded; private boolean mKeyguardShowing; + private boolean mKeyguardQsUserSwitchEnabled; + private boolean mKeyguardUserSwitcherEnabled; private boolean mDozing; private boolean mDozingOnDown; private int mBarState; @@ -465,6 +499,7 @@ public class NotificationPanelViewController extends PanelViewController { private final CommandQueue mCommandQueue; private final NotificationLockscreenUserManager mLockscreenUserManager; + private final UserManager mUserManager; private final ShadeController mShadeController; private final MediaDataManager mMediaDataManager; private int mDisplayId; @@ -557,11 +592,14 @@ public class NotificationPanelViewController extends PanelViewController { StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, + KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory, + KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, + QSDetailDisplayer qsDetailDisplayer, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, AuthController authController, - QSDetailDisplayer qsDetailDisplayer, ScrimController scrimController, + UserManager userManager, MediaDataManager mediaDataManager, AmbientState ambientState, FeatureFlags featureFlags, @@ -582,8 +620,15 @@ public class NotificationPanelViewController extends PanelViewController { mGroupManager = groupManager; mNotificationIconAreaController = notificationIconAreaController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; - mQSDetailDisplayer = qsDetailDisplayer; mFeatureFlags = featureFlags; + mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory; + mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory; + mQSDetailDisplayer = qsDetailDisplayer; + mKeyguardUserSwitcherEnabled = mResources.getBoolean( + com.android.internal.R.bool.config_keyguardUserSwitcher); + mKeyguardQsUserSwitchEnabled = + mKeyguardUserSwitcherEnabled && mResources.getBoolean( + R.bool.config_keyguard_user_switch_opens_qs_details); mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; @@ -599,6 +644,7 @@ public class NotificationPanelViewController extends PanelViewController { mDozeParameters = dozeParameters; mBiometricUnlockController = biometricUnlockController; mScrimController = scrimController; + mUserManager = userManager; mMediaDataManager = mediaDataManager; mControlsComponent = controlsComponent; pulseExpansionHandler.setPulseExpandAbortListener(() -> { @@ -661,9 +707,23 @@ public class NotificationPanelViewController extends PanelViewController { private void onFinishInflate() { loadDimens(); mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); - mKeyguardStatusBar.setQSDetailDisplayer(mQSDetailDisplayer); mBigClockContainer = mView.findViewById(R.id.big_clock_container); - updateViewControllers(mView.findViewById(R.id.keyguard_status_view)); + + UserAvatarView userAvatarView = null; + KeyguardUserSwitcherView keyguardUserSwitcherView = null; + + if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) { + if (mKeyguardQsUserSwitchEnabled) { + ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub); + userAvatarView = (UserAvatarView) stub.inflate(); + } else { + ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub); + keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate(); + } + } + + updateViewControllers(mView.findViewById(R.id.keyguard_status_view), + userAvatarView, keyguardUserSwitcherView); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); NotificationStackScrollLayout stackScrollLayout = mView.findViewById( R.id.notification_stack_scroller); @@ -708,6 +768,10 @@ public class NotificationPanelViewController extends PanelViewController { }); mView.setAccessibilityDelegate(mAccessibilityDelegate); + // dynamically apply the split shade value overrides. + if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { + updateResources(); + } } @Override @@ -736,7 +800,8 @@ public class NotificationPanelViewController extends PanelViewController { R.dimen.heads_up_status_bar_padding); } - private void updateViewControllers(KeyguardStatusView keyguardStatusView) { + private void updateViewControllers(KeyguardStatusView keyguardStatusView, + UserAvatarView userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { // Re-associate the KeyguardStatusViewController KeyguardStatusViewComponent statusViewComponent = mKeyguardStatusViewComponentFactory.build(keyguardStatusView); @@ -747,6 +812,37 @@ public class NotificationPanelViewController extends PanelViewController { KeyguardClockSwitchController keyguardClockSwitchController = statusViewComponent.getKeyguardClockSwitchController(); keyguardClockSwitchController.setBigClockContainer(mBigClockContainer); + + if (mKeyguardUserSwitcherController != null) { + // Try to close the switcher so that callbacks are triggered if necessary. + // Otherwise, NPV can get into a state where some of the views are still hidden + mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false); + mKeyguardUserSwitcherController.removeCallback(); + } + + mKeyguardQsUserSwitchController = null; + mKeyguardUserSwitcherController = null; + + // Re-associate the KeyguardUserSwitcherController + if (userAvatarView != null) { + KeyguardQsUserSwitchComponent userSwitcherComponent = + mKeyguardQsUserSwitchComponentFactory.build(userAvatarView); + mKeyguardQsUserSwitchController = + userSwitcherComponent.getKeyguardQsUserSwitchController(); + mKeyguardQsUserSwitchController.setNotificationPanelViewController(this); + mKeyguardQsUserSwitchController.init(); + mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true); + } else if (keyguardUserSwitcherView != null) { + KeyguardUserSwitcherComponent userSwitcherComponent = + mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView); + mKeyguardUserSwitcherController = + userSwitcherComponent.getKeyguardUserSwitcherController(); + mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener); + mKeyguardUserSwitcherController.init(); + mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true); + } else { + mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false); + } } /** @@ -784,11 +880,21 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setLayoutParams(lp); } + // In order to change the constraints at runtime, all children of the Constraint Layout + // must have ids. + ensureAllViewsHaveIds(mNotificationContainerParent); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mNotificationContainerParent); if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { - // In order to change the constraints at runtime, all children of the Constraint Layout - // must have ids. - ensureAllViewsHaveIds(mNotificationContainerParent); + constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END); + constraintSet.connect( + R.id.notification_stack_scroller, START, + R.id.qs_edge_guideline, START); + } else { + constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END); + constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START); } + constraintSet.applyTo(mNotificationContainerParent); } private static void ensureAllViewsHaveIds(ViewGroup parentView) { @@ -800,7 +906,27 @@ public class NotificationPanelViewController extends PanelViewController { } } + private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) { + View view = mView.findViewById(viewId); + if (view != null) { + int index = mView.indexOfChild(view); + mView.removeView(view); + if (enabled) { + view = mLayoutInflater.inflate(layoutId, mView, false); + mView.addView(view, index); + } else { + view = null; + } + } else if (enabled) { + // It's possible the stub was never inflated if the configuration changed + ViewStub stub = mView.findViewById(stubId); + view = stub.inflate(); + } + return view; + } + private void reInflateViews() { + if (DEBUG) Log.d(TAG, "reInflateViews"); // Re-inflate the status view group. KeyguardStatusView keyguardStatusView = mView.findViewById(R.id.keyguard_status_view); int index = mView.indexOfChild(keyguardStatusView); @@ -809,8 +935,27 @@ public class NotificationPanelViewController extends PanelViewController { R.layout.keyguard_status_view, mView, false); mView.addView(keyguardStatusView, index); + // Re-inflate the keyguard user switcher group. + boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled(); + boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled; + boolean showKeyguardUserSwitcher = + !mKeyguardQsUserSwitchEnabled + && mKeyguardUserSwitcherEnabled + && isUserSwitcherEnabled; + UserAvatarView userAvatarView = (UserAvatarView) reInflateStub( + R.id.keyguard_qs_user_switch_view /* viewId */, + R.id.keyguard_qs_user_switch_stub /* stubId */, + R.layout.keyguard_qs_user_switch /* layoutId */, + showQsUserSwitch /* enabled */); + KeyguardUserSwitcherView keyguardUserSwitcherView = + (KeyguardUserSwitcherView) reInflateStub( + R.id.keyguard_user_switcher_view /* viewId */, + R.id.keyguard_user_switcher_stub /* stubId */, + R.layout.keyguard_user_switcher /* layoutId */, + showKeyguardUserSwitcher /* enabled */); + mBigClockContainer.removeAllViews(); - updateViewControllers(keyguardStatusView); + updateViewControllers(keyguardStatusView, userAvatarView, keyguardUserSwitcherView); // Update keyguard bottom area index = mView.indexOfChild(mKeyguardBottomArea); @@ -834,6 +979,20 @@ public class NotificationPanelViewController extends PanelViewController { false, false, mBarState); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( + mBarState, + false, + false, + mBarState); + } + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility( + mBarState, + false, + false, + mBarState); + } setKeyguardBottomAreaVisibility(mBarState, false); } @@ -926,10 +1085,15 @@ public class NotificationPanelViewController extends PanelViewController { int totalHeight = mView.getHeight(); int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding); int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight); + int userSwitcherPreferredY = mStatusBarMinHeight; boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia(); mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications); + int userIconHeight = mKeyguardQsUserSwitchController != null + ? mKeyguardQsUserSwitchController.getUserIconHeight() + : (mKeyguardUserSwitcherController != null + ? mKeyguardUserSwitcherController.getUserIconHeight() : 0); mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding, mNotificationStackScrollLayoutController.getIntrinsicContentHeight(), getExpandedFraction(), @@ -938,7 +1102,8 @@ public class NotificationPanelViewController extends PanelViewController { ? mKeyguardStatusViewController.getHeight() : (int) (mKeyguardStatusViewController.getHeight() - mShelfHeight / 2.0f - mDarkIconSize / 2.0f), - clockPreferredY, hasCustomClock(), + userIconHeight, + clockPreferredY, userSwitcherPreferredY, hasCustomClock(), hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount, bypassEnabled, getUnlockedStackScrollerPadding(), mUpdateMonitor.canShowLockIcon(), @@ -948,6 +1113,18 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockY, mClockPositionResult.clockScale, animateClock); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.userSwitchY, + animateClock); + } + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.userSwitchY, + animateClock); + } updateNotificationTranslucency(); updateClock(); stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded; @@ -1088,6 +1265,12 @@ public class NotificationPanelViewController extends PanelViewController { private void updateClock() { mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha); + } + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha); + } } public void animateToFullShade(long delay) { @@ -1176,6 +1359,12 @@ public class NotificationPanelViewController extends PanelViewController { } } + public void expandWithQsDetail(DetailAdapter qsDetailAdapter) { + traceQsJank(true /* startTracing */, false /* wasCancelled */); + flingSettings(0 /* velocity */, FLING_EXPAND); + mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0); + } + public void expandWithoutQs() { if (isQsExpanded()) { flingSettings(0 /* velocity */, FLING_COLLAPSE); @@ -1769,8 +1958,9 @@ public class NotificationPanelViewController extends PanelViewController { mBarState != KEYGUARD && (!mQsExpanded || mQsExpansionFromOverscroll)); - if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) { - mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */); + if (mKeyguardUserSwitcherController != null && mQsExpanded + && !mStackScrollerOverscrolling) { + mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true); } if (mQs == null) return; mQs.setExpanded(mQsExpanded); @@ -1837,6 +2027,10 @@ public class NotificationPanelViewController extends PanelViewController { } private float calculateQsTopPadding() { + // in split shade mode we want notifications to be directly below status bar + if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources) && !mKeyguardShowing) { + return 0f; + } if (mKeyguardShowing && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) { @@ -2476,7 +2670,9 @@ public class NotificationPanelViewController extends PanelViewController { super.onTrackingStarted(); if (mQsFullyExpanded) { mQsExpandImmediate = true; - mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true); + if (!Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { + mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true); + } } if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) { mAffordanceHelper.animateHideLeftRightIcon(); @@ -2604,10 +2800,6 @@ public class NotificationPanelViewController extends PanelViewController { } } - public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { - mKeyguardUserSwitcher = keyguardUserSwitcher; - } - public void onScreenTurningOn() { mKeyguardStatusViewController.dozeTimeTick(); } @@ -3074,6 +3266,20 @@ public class NotificationPanelViewController extends PanelViewController { true /* keyguardFadingAway */, false /* goingToFullShade */, mBarState); + if (mKeyguardQsUserSwitchController != null) { + mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( + mBarState, + true /* keyguardFadingAway */, + false /* goingToFullShade */, + mBarState); + } + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility( + mBarState, + true /* keyguardFadingAway */, + false /* goingToFullShade */, + mBarState); + } } /** @@ -3316,6 +3522,50 @@ public class NotificationPanelViewController extends PanelViewController { return mNotificationStackScrollLayoutController; } + /** + * Close the keyguard user switcher if it is open and capable of closing. + * + * Has no effect if user switcher isn't supported, if the user switcher is already closed, or + * if the user switcher uses "simple" mode. The simple user switcher cannot be closed. + * + * @return true if the keyguard user switcher was open, and is now closed + */ + public boolean closeUserSwitcherIfOpen() { + if (mKeyguardUserSwitcherController != null) { + return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple( + true /* animate */); + } + return false; + } + + private void updateUserSwitcherVisibility(boolean open) { + // Do not update if previously called with the same state. + if (mKeyguardUserSwitcherIsShowing == open) { + return; + } + mKeyguardUserSwitcherIsShowing = open; + + if (open) { + animateKeyguardStatusBarOut(); + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + mBarState, + true /* keyguardFadingAway */, + true /* goingToFullShade */, + mBarState); + setKeyguardBottomAreaVisibility(mBarState, true); + mNotificationContainerParent.setVisibility(View.GONE); + } else { + animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + StatusBarState.KEYGUARD, + false, + false, + StatusBarState.SHADE_LOCKED); + setKeyguardBottomAreaVisibility(mBarState, false); + mNotificationContainerParent.setVisibility(View.VISIBLE); + } + } + private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener { @Override public void onHeightChanged(ExpandableView view, boolean needsAnimation) { @@ -3616,6 +3866,7 @@ public class NotificationPanelViewController extends PanelViewController { private class ConfigurationListener implements ConfigurationController.ConfigurationListener { @Override public void onThemeChanged() { + if (DEBUG) Log.d(TAG, "onThemeChanged"); final int themeResId = mView.getContext().getThemeResId(); if (mThemeResId == themeResId) { return; @@ -3627,11 +3878,15 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onOverlayChanged() { + if (DEBUG) Log.d(TAG, "onOverlayChanged"); reInflateViews(); } @Override - public void onUiModeChanged() {} + public void onDensityOrFontScaleChanged() { + if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged"); + reInflateViews(); + } } private class StatusBarStateListener implements StateListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index b36740620d08..e394ebc65a6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -22,8 +22,6 @@ import android.content.res.Configuration; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; -import android.view.ViewStub; -import android.view.ViewStub.OnInflateListener; import android.view.WindowInsets; import android.widget.FrameLayout; @@ -44,14 +42,11 @@ import java.util.Comparator; * The container with notification stack scroller and quick settings inside. */ public class NotificationsQuickSettingsContainer extends ConstraintLayout - implements OnInflateListener, FragmentListener, - AboveShelfObserver.HasViewAboveShelfChangedListener { + implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener { private FrameLayout mQsFrame; - private View mUserSwitcher; private NotificationStackScrollLayout mStackScroller; private View mKeyguardStatusBar; - private boolean mInflated; private boolean mQsExpanded; private boolean mCustomizerAnimating; @@ -73,9 +68,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout mStackScroller = findViewById(R.id.notification_stack_scroller); mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin; mKeyguardStatusBar = findViewById(R.id.keyguard_header); - ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher); - userSwitcher.setOnInflateListener(this); - mUserSwitcher = userSwitcher; } @Override @@ -119,10 +111,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout // touches first but the panel gets drawn above. mDrawingOrderedChildren.clear(); mLayoutDrawingOrder.clear(); - if (mInflated && mUserSwitcher.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mUserSwitcher); - mLayoutDrawingOrder.add(mUserSwitcher); - } if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) { mDrawingOrderedChildren.add(mKeyguardStatusBar); mLayoutDrawingOrder.add(mKeyguardStatusBar); @@ -158,14 +146,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } @Override - public void onInflate(ViewStub stub, View inflated) { - if (stub == mUserSwitcher) { - mUserSwitcher = inflated; - mInflated = true; - } - } - - @Override public void onFragmentViewCreated(String tag, Fragment fragment) { QS container = (QS) fragment; container.setContainer(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1e19beeff730..041a97e1d404 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -89,7 +89,6 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.os.UserManager; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; @@ -226,7 +225,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; @@ -623,7 +621,6 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private KeyguardUserSwitcher mKeyguardUserSwitcher; private final UserSwitcherController mUserSwitcherController; private final NetworkController mNetworkController; private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @@ -1212,9 +1209,6 @@ public class StatusBar extends SystemUI implements DemoMode, }); mNotificationPanelViewController.setUserSetupComplete(mUserSetup); - if (UserManager.get(mContext).isUserSwitcherEnabled()) { - createUserSwitcher(); - } // Set up the quick settings tile panel final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame); @@ -1441,9 +1435,6 @@ public class StatusBar extends SystemUI implements DemoMode, // TODO: Bring these out of StatusBar. mUserInfoControllerImpl.onDensityOrFontScaleChanged(); mUserSwitcherController.onDensityOrFontScaleChanged(); - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.onDensityOrFontScaleChanged(); - } mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); mHeadsUpManager.onDensityOrFontScaleChanged(); } @@ -1477,13 +1468,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected void createUserSwitcher() { - mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext, - mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher), - mNotificationShadeWindowView.findViewById(R.id.keyguard_header), - mNotificationPanelViewController); - } - private void inflateStatusBarWindow() { mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView(); StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get() @@ -3266,7 +3250,7 @@ public class StatusBar extends SystemUI implements DemoMode, mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) { mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER); - } else if (!mPulseExpansionHandler.isWakingToShadeLocked()){ + } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) { mStatusBarStateController.setState(StatusBarState.KEYGUARD); } updatePanelExpansionForKeyguard(); @@ -3565,15 +3549,15 @@ public class StatusBar extends SystemUI implements DemoMode, } return true; } + if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) { + return true; + } if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { mShadeController.animateCollapsePanels(); } return true; } - if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) { - return true; - } return false; } @@ -3622,20 +3606,8 @@ public class StatusBar extends SystemUI implements DemoMode, updateTheme(); mNavigationBarController.touchAutoDim(mDisplayId); Trace.beginSection("StatusBar#updateKeyguardState"); - if (mState == StatusBarState.KEYGUARD) { - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.setKeyguard(true, - mStatusBarStateController.fromShadeLocked()); - } - if (mStatusBarView != null) mStatusBarView.removePendingHideExpandedRunnables(); - } else { - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.setKeyguard(false, - mStatusBarStateController.goingToFullShade() || - mState == StatusBarState.SHADE_LOCKED || - mStatusBarStateController.fromShadeLocked()); - } - + if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) { + mStatusBarView.removePendingHideExpandedRunnables(); } updateDozingState(); checkBarModes(); 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 00acd7bb6707..862037617374 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -42,8 +42,8 @@ import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarMobileView; import com.android.systemui.statusbar.StatusBarWifiView; import com.android.systemui.statusbar.StatusIconDisplayable; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; -import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import java.util.List; @@ -66,7 +66,7 @@ public interface StatusBarIconController { /** * Display the no calling & SMS icons. */ - void setNoCallingIcons(String slot, List<NoCallingIconState> states); + void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states); public void setIconVisibility(String slot, boolean b); /** 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 5e8d59041fab..f0c8527bcb7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -34,8 +34,8 @@ import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusIconDisplayable; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; -import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -206,6 +206,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu Collections.reverse(iconStates); for (MobileIconState state : iconStates) { + StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId); if (holder == null) { holder = StatusBarIconHolder.fromMobileIconState(state); @@ -218,23 +219,25 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu } /** - * Accept a list of NoCallingIconStates, and show them in the same slot + * Accept a list of CallIndicatorIconStates, and show them in the same slot * @param slot StatusBar slot * @param states All of the no Calling & SMS icon states */ @Override - public void setNoCallingIcons(String slot, List<NoCallingIconState> states) { + public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) { Slot noCallingSlot = getSlot(slot); int slotIndex = getSlotIndex(slot); - - for (NoCallingIconState state : states) { + for (CallIndicatorIconState state : states) { StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId); if (holder == null) { - holder = StatusBarIconHolder.fromNoCallingState(mContext, state); - holder.setVisible(state.visible); + holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state); setIcon(slotIndex, holder); } else { - holder.setVisible(state.visible); + int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId; + String contentDescription = state.isNoCalling + ? state.noCallingDescription : state.callStrengthDescription; + holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), + Icon.createWithResource(mContext, resId), 0, 0, contentDescription)); setIcon(slotIndex, holder); } } 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 36a0e63db19f..a1a2d30e9b00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java @@ -22,8 +22,8 @@ import android.graphics.drawable.Icon; import android.os.UserHandle; import com.android.internal.statusbar.StatusBarIcon; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; -import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; /** @@ -72,14 +72,18 @@ public class StatusBarIconHolder { } /** - * Creates a new StatusBarIconHolder from a NoCallingIconState. + * Creates a new StatusBarIconHolder from a CallIndicatorIconState. */ - public static StatusBarIconHolder fromNoCallingState( - Context context, NoCallingIconState state) { + public static StatusBarIconHolder fromCallIndicatorState( + Context context, CallIndicatorIconState state) { StatusBarIconHolder holder = new StatusBarIconHolder(); + int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId; + String contentDescription = state.isNoCalling + ? state.noCallingDescription : state.callStrengthDescription; holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(), - Icon.createWithResource(context, state.resId), 0, 0, null); + Icon.createWithResource(context, resId), 0, 0, contentDescription); holder.mTag = state.subId; + holder.setVisible(true); return holder; } @@ -92,6 +96,10 @@ public class StatusBarIconHolder { return mIcon; } + public void setIcon(StatusBarIcon icon) { + mIcon = icon; + } + @Nullable public WifiIconState getWifiState() { return mWifiState; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java index f6165f666c89..7bc1bb39642b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -22,6 +22,7 @@ import android.telephony.SubscriptionInfo; import android.util.ArraySet; import android.util.Log; +import com.android.settingslib.mobile.TelephonyIcons; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController; @@ -39,7 +40,7 @@ import java.util.Objects; public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback, SecurityController.SecurityControllerCallback, Tunable { private static final String TAG = "StatusBarSignalPolicy"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.INFO); private final String mSlotAirplane; private final String mSlotMobile; @@ -67,7 +68,8 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba private boolean mWifiVisible = false; private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>(); - private ArrayList<NoCallingIconState> mNoCallingStates = new ArrayList<NoCallingIconState>(); + private ArrayList<CallIndicatorIconState> mCallIndicatorStates = + new ArrayList<CallIndicatorIconState>(); private WifiIconState mWifiIconState = new WifiIconState(); public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) { @@ -201,19 +203,25 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } @Override - public void setNoCallingStatus(boolean noCalling, int subId) { + public void setCallIndicator(IconState statusIcon, int subId) { if (DEBUG) { - Log.d(TAG, "setNoCallingStatus: " - + "noCalling = " + noCalling + "," + Log.d(TAG, "setCallIndicator: " + + "statusIcon = " + statusIcon + "," + "subId = " + subId); } - NoCallingIconState state = getNoCallingState(subId); + CallIndicatorIconState state = getNoCallingState(subId); if (state == null) { return; } - state.visible = noCalling; - mIconController.setNoCallingIcons( - mSlotNoCalling, NoCallingIconState.copyStates(mNoCallingStates)); + if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) { + state.isNoCalling = statusIcon.visible; + state.noCallingDescription = statusIcon.contentDescription; + } else { + state.callStrengthResId = statusIcon.icon; + state.callStrengthDescription = statusIcon.contentDescription; + } + mIconController.setCallIndicatorIcons( + mSlotNoCalling, CallIndicatorIconState.copyStates(mCallIndicatorStates)); } @Override @@ -273,8 +281,8 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } } - private NoCallingIconState getNoCallingState(int subId) { - for (NoCallingIconState state : mNoCallingStates) { + private CallIndicatorIconState getNoCallingState(int subId) { + for (CallIndicatorIconState state : mCallIndicatorStates) { if (state.subId == subId) { return state; } @@ -315,23 +323,25 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba } mIconController.removeAllIconsForSlot(mSlotMobile); + mIconController.removeAllIconsForSlot(mSlotNoCalling); mMobileStates.clear(); - List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>(); - noCallingStates.addAll(mNoCallingStates); - mNoCallingStates.clear(); + List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>(); + noCallingStates.addAll(mCallIndicatorStates); + mCallIndicatorStates.clear(); final int n = subs.size(); for (int i = 0; i < n; i++) { mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId())); boolean isNewSub = true; - for (NoCallingIconState state : noCallingStates) { + for (CallIndicatorIconState state : noCallingStates) { if (state.subId == subs.get(i).getSubscriptionId()) { - mNoCallingStates.add(state); + mCallIndicatorStates.add(state); isNewSub = false; break; } } if (isNewSub) { - mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId())); + mCallIndicatorStates.add( + new CallIndicatorIconState(subs.get(i).getSubscriptionId())); } } } @@ -425,14 +435,18 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba /** * Stores the StatusBar state for no Calling & SMS. */ - public static class NoCallingIconState { - public boolean visible; - public int resId; + public static class CallIndicatorIconState { + public boolean isNoCalling; + public int noCallingResId; + public int callStrengthResId; public int subId; + public String noCallingDescription; + public String callStrengthDescription; - private NoCallingIconState(int subId) { + private CallIndicatorIconState(int subId) { this.subId = subId; - this.resId = R.drawable.ic_qs_no_calling_sms; + this.noCallingResId = R.drawable.ic_qs_no_calling_sms; + this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0]; } @Override @@ -441,27 +455,36 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba if (o == null || getClass() != o.getClass()) { return false; } - NoCallingIconState that = (NoCallingIconState) o; - return visible == that.visible - && resId == that.resId - && subId == that.subId; + CallIndicatorIconState that = (CallIndicatorIconState) o; + return isNoCalling == that.isNoCalling + && noCallingResId == that.noCallingResId + && callStrengthResId == that.callStrengthResId + && subId == that.subId + && noCallingDescription == that.noCallingDescription + && callStrengthDescription == that.callStrengthDescription; + } @Override public int hashCode() { - return Objects.hash(visible, resId, subId); + return Objects.hash(isNoCalling, noCallingResId, + callStrengthResId, subId, noCallingDescription, callStrengthDescription); } - private void copyTo(NoCallingIconState other) { - other.visible = visible; - other.resId = resId; + private void copyTo(CallIndicatorIconState other) { + other.isNoCalling = isNoCalling; + other.noCallingResId = noCallingResId; + other.callStrengthResId = callStrengthResId; other.subId = subId; + other.noCallingDescription = noCallingDescription; + other.callStrengthDescription = callStrengthDescription; } - private static List<NoCallingIconState> copyStates(List<NoCallingIconState> inStates) { - ArrayList<NoCallingIconState> outStates = new ArrayList<>(); - for (NoCallingIconState state : inStates) { - NoCallingIconState copy = new NoCallingIconState(state.subId); + private static List<CallIndicatorIconState> copyStates( + List<CallIndicatorIconState> inStates) { + ArrayList<CallIndicatorIconState> outStates = new ArrayList<>(); + for (CallIndicatorIconState state : inStates) { + CallIndicatorIconState copy = new CallIndicatorIconState(state.subId); state.copyTo(copy); outStates.add(copy); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java index ccaa1f480683..5ff897029543 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java @@ -36,6 +36,7 @@ import java.util.List; * the current or specified Looper. */ public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback { + private static final String TAG = "CallbackHandler"; private static final int MSG_EMERGENCE_CHANGED = 0; private static final int MSG_SUBS_CHANGED = 1; private static final int MSG_NO_SIM_VISIBLE_CHANGED = 2; @@ -198,17 +199,17 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa } @Override - public void setNoCallingStatus(boolean noCalling, int subId) { + public void setCallIndicator(IconState statusIcon, int subId) { String log = new StringBuilder() .append(SSDF.format(System.currentTimeMillis())).append(",") - .append("setNoCallingStatus: ") - .append("noCalling=").append(noCalling).append(",") + .append("setCallIndicator: ") + .append("statusIcon=").append(statusIcon).append(",") .append("subId=").append(subId) .toString(); recordLastCallback(log); post(() -> { for (SignalCallback signalCluster : mSignalCallbacks) { - signalCluster.setNoCallingStatus(noCalling, subId); + signalCluster.setCallIndicator(statusIcon, subId); } }); } @@ -226,24 +227,11 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa @Override public void setNoSims(boolean show, boolean simDetected) { - String log = new StringBuilder() - .append(SSDF.format(System.currentTimeMillis())).append(",") - .append("setNoSims: ") - .append("show=").append(show).append(",") - .append("simDetected=").append(simDetected) - .toString(); - recordLastCallback(log); obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget(); } @Override public void setMobileDataEnabled(boolean enabled) { - String log = new StringBuilder() - .append(SSDF.format(System.currentTimeMillis())).append(",") - .append("setMobileDataEnabled: ") - .append("enabled=").append(enabled) - .toString(); - recordLastCallback(log); obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget(); } @@ -283,7 +271,8 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa } protected void recordLastCallback(String callback) { - mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback; + mHistory[mHistoryIndex] = callback; + mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; } /** @@ -293,7 +282,9 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa pw.println(" - CallbackHandler -----"); int size = 0; for (int i = 0; i < HISTORY_SIZE; i++) { - if (mHistory[i] != null) size++; + if (mHistory[i] != null) { + size++; + } } // Print out the previous states in ordered number. for (int i = mHistoryIndex + HISTORY_SIZE - 1; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java new file mode 100644 index 000000000000..38f3bc891394 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.logging.UiEventLogger; +import com.android.keyguard.KeyguardConstants; +import com.android.keyguard.KeyguardVisibilityHelper; +import com.android.keyguard.dagger.KeyguardUserSwitcherScope; +import com.android.settingslib.drawable.CircleFramedDrawable; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.notification.stack.AnimationProperties; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.NotificationPanelViewController; +import com.android.systemui.statusbar.phone.UserAvatarView; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; + +/** + * Manages the user switch on the Keyguard that is used for opening the QS user panel. + */ +@KeyguardUserSwitcherScope +public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> { + + private static final String TAG = "KeyguardQsUserSwitchController"; + private static final boolean DEBUG = KeyguardConstants.DEBUG; + + private static final AnimationProperties ANIMATION_PROPERTIES = + new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + + private final Context mContext; + private Resources mResources; + private final UserSwitcherController mUserSwitcherController; + private final ScreenLifecycle mScreenLifecycle; + private UserSwitcherController.BaseUserAdapter mAdapter; + private final KeyguardStateController mKeyguardStateController; + protected final SysuiStatusBarStateController mStatusBarStateController; + private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; + private final KeyguardUserDetailAdapter mUserDetailAdapter; + private NotificationPanelViewController mNotificationPanelViewController; + private UserManager mUserManager; + UserSwitcherController.UserRecord mCurrentUser; + + // State info for the user switch and keyguard + private int mBarState; + private float mDarkAmount; + + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStateChanged(int newState) { + if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState)); + + boolean goingToFullShade = mStatusBarStateController.goingToFullShade(); + boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway(); + int oldState = mBarState; + mBarState = newState; + + setKeyguardQsUserSwitchVisibility( + newState, + keyguardFadingAway, + goingToFullShade, + oldState); + } + + @Override + public void onDozeAmountChanged(float linearAmount, float amount) { + if (DEBUG) { + Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f", + linearAmount, amount)); + } + setDarkAmount(amount); + } + }; + + @Inject + public KeyguardQsUserSwitchController( + UserAvatarView view, + Context context, + @Main Resources resources, + UserManager userManager, + ScreenLifecycle screenLifecycle, + UserSwitcherController userSwitcherController, + KeyguardStateController keyguardStateController, + SysuiStatusBarStateController statusBarStateController, + DozeParameters dozeParameters, + UiEventLogger uiEventLogger) { + super(view); + if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); + mContext = context; + mResources = resources; + mUserManager = userManager; + mScreenLifecycle = screenLifecycle; + mUserSwitcherController = userSwitcherController; + mKeyguardStateController = keyguardStateController; + mStatusBarStateController = statusBarStateController; + mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, + keyguardStateController, dozeParameters); + mUserDetailAdapter = new KeyguardUserDetailAdapter(mUserSwitcherController, mContext, + uiEventLogger); + } + + @Override + protected void onInit() { + super.onInit(); + if (DEBUG) Log.d(TAG, "onInit"); + mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return null; + } + }; + + mView.setOnClickListener(v -> { + if (isListAnimating()) { + return; + } + + // Tapping anywhere in the view will open QS user panel + openQsUserPanel(); + }); + + updateView(true /* forceUpdate */); + } + + @Override + protected void onViewAttached() { + if (DEBUG) Log.d(TAG, "onViewAttached"); + mAdapter.registerDataSetObserver(mDataSetObserver); + mDataSetObserver.onChanged(); + mStatusBarStateController.addCallback(mStatusBarStateListener); + } + + @Override + protected void onViewDetached() { + if (DEBUG) Log.d(TAG, "onViewDetached"); + + mAdapter.unregisterDataSetObserver(mDataSetObserver); + mStatusBarStateController.removeCallback(mStatusBarStateListener); + } + + public final DataSetObserver mDataSetObserver = new DataSetObserver() { + @Override + public void onChanged() { + updateView(false /* forceUpdate */); + } + }; + + /** + * @return true if the current user has changed + */ + private boolean updateCurrentUser() { + UserSwitcherController.UserRecord previousUser = mCurrentUser; + mCurrentUser = null; + for (int i = 0; i < mAdapter.getCount(); i++) { + UserSwitcherController.UserRecord r = mAdapter.getItem(i); + if (r.isCurrent) { + mCurrentUser = r; + return !mCurrentUser.equals(previousUser); + } + } + return mCurrentUser == null && previousUser != null; + } + + /** + * @param forceUpdate whether to update view even if current user did not change + */ + private void updateView(boolean forceUpdate) { + if (!updateCurrentUser() && !forceUpdate) { + return; + } + + if (mCurrentUser == null) { + mView.setVisibility(View.GONE); + return; + } + + mView.setVisibility(View.VISIBLE); + + String currentUserName = mCurrentUser.info.name; + String contentDescription = null; + + if (!TextUtils.isEmpty(currentUserName)) { + contentDescription = mContext.getString( + R.string.accessibility_quick_settings_user, + currentUserName); + } + + if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) { + mView.setContentDescription(contentDescription); + } + + if (mCurrentUser.picture == null) { + mView.setDrawableWithBadge(getDrawable(mCurrentUser).mutate(), + mCurrentUser.resolveId()); + } else { + int avatarSize = + (int) mResources.getDimension(R.dimen.kg_framed_avatar_size); + Drawable drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize); + drawable.setColorFilter( + mCurrentUser.isSwitchToEnabled ? null + : mAdapter.getDisabledUserAvatarColorFilter()); + mView.setDrawableWithBadge(drawable, mCurrentUser.info.id); + } + } + + Drawable getDrawable(UserSwitcherController.UserRecord item) { + Drawable drawable; + if (item.isCurrent && item.isGuest) { + drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); + } else { + drawable = mAdapter.getIconDrawable(mContext, item); + } + + int iconColorRes; + if (item.isSwitchToEnabled) { + iconColorRes = R.color.kg_user_switcher_avatar_icon_color; + } else { + iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color; + } + drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); + + Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar); + drawable = new LayerDrawable(new Drawable[]{bg, drawable}); + return drawable; + } + + /** + * Get the height of the keyguard user switcher view when closed. + */ + public int getUserIconHeight() { + return mView.getHeight(); + } + + /** + * Set the visibility of the user avatar view based on some new state. + */ + public void setKeyguardQsUserSwitchVisibility( + int statusBarState, + boolean keyguardFadingAway, + boolean goingToFullShade, + int oldStatusBarState) { + mKeyguardVisibilityHelper.setViewVisibility( + statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); + } + + /** + * Update position of the view with an optional animation + */ + public void updatePosition(int x, int y, boolean animate) { + PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate); + PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), + ANIMATION_PROPERTIES, animate); + } + + /** + * Set keyguard user avatar view alpha. + */ + public void setAlpha(float alpha) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { + mView.setAlpha(alpha); + } + } + + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ + private void setDarkAmount(float darkAmount) { + boolean isAwake = darkAmount != 0; + if (darkAmount == mDarkAmount) { + return; + } + mDarkAmount = darkAmount; + mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); + } + + private boolean isListAnimating() { + return mKeyguardVisibilityHelper.isVisibilityAnimating(); + } + + private void openQsUserPanel() { + mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter); + } + + public void setNotificationPanelViewController( + NotificationPanelViewController notificationPanelViewController) { + mNotificationPanelViewController = notificationPanelViewController; + } + + class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter { + KeyguardUserDetailAdapter(UserSwitcherController userSwitcherController, Context context, + UiEventLogger uiEventLogger) { + super(userSwitcherController, context, uiEventLogger); + } + + @Override + public boolean shouldAnimate() { + return false; + } + + @Override + public boolean onDoneButtonClicked() { + if (mNotificationPanelViewController != null) { + mNotificationPanelViewController.animateCloseQs(true /* animateAway */); + return true; + } else { + return false; + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java index 07433e13104c..0649478a42aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java @@ -17,8 +17,15 @@ package com.android.systemui.statusbar.policy; import android.content.Context; +import android.graphics.Color; import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import androidx.core.graphics.ColorUtils; + +import com.android.keyguard.KeyguardConstants; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.qs.tiles.UserDetailItemView; @@ -27,6 +34,14 @@ import com.android.systemui.qs.tiles.UserDetailItemView; */ public class KeyguardUserDetailItemView extends UserDetailItemView { + private static final String TAG = "KeyguardUserDetailItemView"; + private static final boolean DEBUG = KeyguardConstants.DEBUG; + + private static final int ANIMATION_DURATION_FADE_NAME = 240; + + private float mDarkAmount; + private int mTextColor; + public KeyguardUserDetailItemView(Context context) { this(context, null); } @@ -48,4 +63,89 @@ public class KeyguardUserDetailItemView extends UserDetailItemView { protected int getFontSizeDimen() { return R.dimen.kg_user_switcher_text_size; } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mTextColor = mName.getCurrentTextColor(); + updateDark(); + } + + /** + * Update visibility of this view. + * + * @param showItem If true, this item is visible on the screen to the user. Generally this + * means that the item would be clickable. If false, item visibility will be + * set to GONE and hidden entirely. + * @param showTextName Whether or not the name should be shown next to the icon. If false, + * only the icon is shown. + * @param animate Whether the transition should be animated. Note, this only applies to + * animating the text name. The item itself will not animate (i.e. fade in/out). + * Instead, we delegate that to the parent view. + */ + void updateVisibilities(boolean showItem, boolean showTextName, boolean animate) { + if (DEBUG) { + Log.d(TAG, String.format("updateVisibilities itemIsShown=%b nameIsShown=%b animate=%b", + showItem, showTextName, animate)); + } + + getBackground().setAlpha((showItem && showTextName) ? 255 : 0); + + if (showItem) { + if (showTextName) { + mName.setVisibility(View.VISIBLE); + if (animate) { + mName.setAlpha(0f); + mName.animate() + .alpha(1f) + .setDuration(ANIMATION_DURATION_FADE_NAME) + .setInterpolator(Interpolators.ALPHA_IN); + } else { + mName.setAlpha(1f); + } + } else { + if (animate) { + mName.setVisibility(View.VISIBLE); + mName.setAlpha(1f); + mName.animate() + .alpha(0f) + .setDuration(ANIMATION_DURATION_FADE_NAME) + .setInterpolator(Interpolators.ALPHA_OUT) + .withEndAction(() -> { + mName.setVisibility(View.GONE); + mName.setAlpha(1f); + }); + } else { + mName.setVisibility(View.GONE); + mName.setAlpha(1f); + } + } + setVisibility(View.VISIBLE); + setAlpha(1f); + } else { + // If item isn't shown, don't animate. The parent class will animate the view instead + setVisibility(View.GONE); + setAlpha(1f); + mName.setVisibility(showTextName ? View.VISIBLE : View.GONE); + mName.setAlpha(1f); + } + } + + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ + public void setDarkAmount(float darkAmount) { + if (mDarkAmount == darkAmount) { + return; + } + mDarkAmount = darkAmount; + updateDark(); + } + + private void updateDark() { + final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); + mName.setTextColor(blendedTextColor); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java deleted file mode 100644 index 90f557753132..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.policy; - -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; -import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.database.DataSetObserver; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewStub; -import android.widget.FrameLayout; - -import com.android.settingslib.animation.AppearAnimationUtils; -import com.android.settingslib.drawable.CircleFramedDrawable; -import com.android.systemui.Dependency; -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.qs.tiles.UserDetailItemView; -import com.android.systemui.statusbar.phone.KeyguardStatusBarView; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; - -import java.util.ArrayList; - -/** - * Manages the user switcher on the Keyguard. - */ -public class KeyguardUserSwitcher { - - private static final String TAG = "KeyguardUserSwitcher"; - private static final boolean ALWAYS_ON = false; - - private final Container mUserSwitcherContainer; - private final KeyguardStatusBarView mStatusBarView; - private final KeyguardUserAdapter mAdapter; - private final AppearAnimationUtils mAppearAnimationUtils; - private final KeyguardUserSwitcherScrim mBackground; - - private ViewGroup mUserSwitcher; - private ObjectAnimator mBgAnimator; - private UserSwitcherController mUserSwitcherController; - private boolean mAnimating; - - public KeyguardUserSwitcher(Context context, ViewStub userSwitcher, - KeyguardStatusBarView statusBarView, - NotificationPanelViewController panelViewController) { - boolean keyguardUserSwitcherEnabled = - context.getResources().getBoolean( - com.android.internal.R.bool.config_keyguardUserSwitcher) || ALWAYS_ON; - UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class); - if (userSwitcherController != null && keyguardUserSwitcherEnabled) { - mUserSwitcherContainer = (Container) userSwitcher.inflate(); - mBackground = new KeyguardUserSwitcherScrim(context); - reinflateViews(); - mStatusBarView = statusBarView; - mStatusBarView.setKeyguardUserSwitcher(this); - panelViewController.setKeyguardUserSwitcher(this); - mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this); - mAdapter.registerDataSetObserver(mDataSetObserver); - mUserSwitcherController = userSwitcherController; - mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f, - Interpolators.FAST_OUT_SLOW_IN); - mUserSwitcherContainer.setKeyguardUserSwitcher(this); - } else { - mUserSwitcherContainer = null; - mStatusBarView = null; - mAdapter = null; - mAppearAnimationUtils = null; - mBackground = null; - } - } - - private void reinflateViews() { - if (mUserSwitcher != null) { - mUserSwitcher.setBackground(null); - mUserSwitcher.removeOnLayoutChangeListener(mBackground); - } - mUserSwitcherContainer.removeAllViews(); - - LayoutInflater.from(mUserSwitcherContainer.getContext()) - .inflate(R.layout.keyguard_user_switcher_inner, mUserSwitcherContainer); - - mUserSwitcher = (ViewGroup) mUserSwitcherContainer.findViewById( - R.id.keyguard_user_switcher_inner); - mUserSwitcher.addOnLayoutChangeListener(mBackground); - mUserSwitcher.setBackground(mBackground); - } - - public void setKeyguard(boolean keyguard, boolean animate) { - if (mUserSwitcher != null) { - if (keyguard && shouldExpandByDefault()) { - show(animate); - } else { - hide(animate); - } - } - } - - /** - * @return true if the user switcher should be expanded by default on the lock screen. - * @see android.os.UserManager#isUserSwitcherEnabled() - */ - private boolean shouldExpandByDefault() { - return (mUserSwitcherController != null) && mUserSwitcherController.isSimpleUserSwitcher(); - } - - public void show(boolean animate) { - if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() != View.VISIBLE) { - cancelAnimations(); - mAdapter.refresh(); - mUserSwitcherContainer.setVisibility(View.VISIBLE); - mStatusBarView.setKeyguardUserSwitcherShowing(true, animate); - if (animate) { - startAppearAnimation(); - } - } - } - - private boolean hide(boolean animate) { - if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) { - cancelAnimations(); - if (animate) { - startDisappearAnimation(); - } else { - mUserSwitcherContainer.setVisibility(View.GONE); - } - mStatusBarView.setKeyguardUserSwitcherShowing(false, animate); - return true; - } - return false; - } - - private void cancelAnimations() { - int count = mUserSwitcher.getChildCount(); - for (int i = 0; i < count; i++) { - mUserSwitcher.getChildAt(i).animate().cancel(); - } - if (mBgAnimator != null) { - mBgAnimator.cancel(); - } - mUserSwitcher.animate().cancel(); - mAnimating = false; - } - - private void startAppearAnimation() { - int count = mUserSwitcher.getChildCount(); - View[] objects = new View[count]; - for (int i = 0; i < count; i++) { - objects[i] = mUserSwitcher.getChildAt(i); - } - mUserSwitcher.setClipChildren(false); - mUserSwitcher.setClipToPadding(false); - mAppearAnimationUtils.startAnimation(objects, new Runnable() { - @Override - public void run() { - mUserSwitcher.setClipChildren(true); - mUserSwitcher.setClipToPadding(true); - } - }); - mAnimating = true; - mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255); - mBgAnimator.setDuration(400); - mBgAnimator.setInterpolator(Interpolators.ALPHA_IN); - mBgAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mBgAnimator = null; - mAnimating = false; - } - }); - mBgAnimator.start(); - } - - private void startDisappearAnimation() { - mAnimating = true; - mUserSwitcher.animate() - .alpha(0f) - .setDuration(300) - .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction(new Runnable() { - @Override - public void run() { - mUserSwitcherContainer.setVisibility(View.GONE); - mUserSwitcher.setAlpha(1f); - mAnimating = false; - } - }); - } - - private void refresh() { - final int childCount = mUserSwitcher.getChildCount(); - final int adapterCount = mAdapter.getCount(); - final int N = Math.max(childCount, adapterCount); - for (int i = 0; i < N; i++) { - if (i < adapterCount) { - View oldView = null; - if (i < childCount) { - oldView = mUserSwitcher.getChildAt(i); - } - View newView = mAdapter.getView(i, oldView, mUserSwitcher); - if (oldView == null) { - // We ran out of existing views. Add it at the end. - mUserSwitcher.addView(newView); - } else if (oldView != newView) { - // We couldn't rebind the view. Replace it. - mUserSwitcher.removeViewAt(i); - mUserSwitcher.addView(newView, i); - } - } else { - int lastIndex = mUserSwitcher.getChildCount() - 1; - mUserSwitcher.removeViewAt(lastIndex); - } - } - } - - public boolean hideIfNotSimple(boolean animate) { - if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) { - return hide(animate); - } - return false; - } - - boolean isAnimating() { - return mAnimating; - } - - public final DataSetObserver mDataSetObserver = new DataSetObserver() { - @Override - public void onChanged() { - refresh(); - } - }; - - public void onDensityOrFontScaleChanged() { - if (mUserSwitcherContainer != null) { - reinflateViews(); - refresh(); - } - } - - static class KeyguardUserAdapter extends - UserSwitcherController.BaseUserAdapter implements View.OnClickListener { - - private Context mContext; - private KeyguardUserSwitcher mKeyguardUserSwitcher; - private View mCurrentUserView; - // List of users where the first entry is always the current user - private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>(); - - KeyguardUserAdapter(Context context, UserSwitcherController controller, - KeyguardUserSwitcher kgu) { - super(controller); - mContext = context; - mKeyguardUserSwitcher = kgu; - } - - @Override - public void notifyDataSetChanged() { - refreshUserOrder(); - super.notifyDataSetChanged(); - } - - void refreshUserOrder() { - ArrayList<UserSwitcherController.UserRecord> users = super.getUsers(); - mUsersOrdered = new ArrayList<>(users.size()); - for (int i = 0; i < users.size(); i++) { - UserSwitcherController.UserRecord record = users.get(i); - if (record.isCurrent) { - mUsersOrdered.add(0, record); - } else { - mUsersOrdered.add(record); - } - } - } - - @Override - protected ArrayList<UserSwitcherController.UserRecord> getUsers() { - return mUsersOrdered; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - UserSwitcherController.UserRecord item = getItem(position); - return createUserDetailItemView(convertView, parent, item); - } - - KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) { - if (!(convertView instanceof KeyguardUserDetailItemView) - || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) { - convertView = LayoutInflater.from(mContext).inflate( - R.layout.keyguard_user_switcher_item, parent, false); - } - return (KeyguardUserDetailItemView) convertView; - } - - UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent, - UserSwitcherController.UserRecord item) { - KeyguardUserDetailItemView v = convertOrInflate(convertView, parent); - if (!item.isCurrent || item.isGuest) { - v.setOnClickListener(this); - } else { - v.setOnClickListener(null); - v.setClickable(false); - } - - String name = getName(mContext, item); - if (item.picture == null) { - v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId()); - } else { - int avatarSize = - (int) mContext.getResources().getDimension(R.dimen.kg_framed_avatar_size); - Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize); - drawable.setColorFilter( - item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter()); - v.bind(name, drawable, item.info.id); - } - v.setActivated(item.isCurrent); - v.setDisabledByAdmin(item.isDisabledByAdmin); - v.setEnabled(item.isSwitchToEnabled); - v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); - - if (item.isCurrent) { - mCurrentUserView = v; - } - v.setTag(item); - return v; - } - - private static Drawable getDrawable(Context context, - UserSwitcherController.UserRecord item) { - Drawable drawable = getIconDrawable(context, item); - int iconColorRes; - if (item.isCurrent) { - iconColorRes = R.color.kg_user_switcher_selected_avatar_icon_color; - } else if (!item.isSwitchToEnabled) { - iconColorRes = R.color.GM2_grey_600; - } else { - iconColorRes = R.color.kg_user_switcher_avatar_icon_color; - } - drawable.setTint(context.getResources().getColor(iconColorRes, context.getTheme())); - - if (item.isCurrent) { - Drawable bg = context.getDrawable(R.drawable.bg_avatar_selected); - drawable = new LayerDrawable(new Drawable[]{bg, drawable}); - } - - return drawable; - } - - @Override - public void onClick(View v) { - UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag(); - if (user.isCurrent && !user.isGuest) { - // Close the switcher if tapping the current user. Guest is excluded because - // tapping the guest user while it's current clears the session. - mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */); - } else if (user.isSwitchToEnabled) { - if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) { - if (mCurrentUserView != null) { - mCurrentUserView.setActivated(false); - } - v.setActivated(true); - } - onUserListItemClicked(user); - } - } - } - - public static class Container extends FrameLayout { - - private KeyguardUserSwitcher mKeyguardUserSwitcher; - - public Container(Context context, AttributeSet attrs) { - super(context, attrs); - setClipChildren(false); - } - - public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { - mKeyguardUserSwitcher = keyguardUserSwitcher; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - // Hide switcher if it didn't handle the touch event (and let the event go through). - if (mKeyguardUserSwitcher != null && !mKeyguardUserSwitcher.isAnimating()) { - mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */); - } - return false; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java new file mode 100644 index 000000000000..b76e451cb681 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; +import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; + +import android.content.Context; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.UserHandle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import com.android.keyguard.KeyguardConstants; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.KeyguardVisibilityHelper; +import com.android.keyguard.dagger.KeyguardUserSwitcherScope; +import com.android.settingslib.drawable.CircleFramedDrawable; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.notification.stack.AnimationProperties; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.ViewController; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +import javax.inject.Inject; + +/** + * Manages the user switcher on the Keyguard. + */ +@KeyguardUserSwitcherScope +public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> { + + private static final String TAG = "KeyguardUserSwitcherController"; + private static final boolean DEBUG = KeyguardConstants.DEBUG; + + private static final AnimationProperties ANIMATION_PROPERTIES = + new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + + private final Context mContext; + private final UserSwitcherController mUserSwitcherController; + private final ScreenLifecycle mScreenLifecycle; + private final KeyguardUserAdapter mAdapter; + private final KeyguardStateController mKeyguardStateController; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback; + protected final SysuiStatusBarStateController mStatusBarStateController; + private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; + + // Child views of KeyguardUserSwitcherView + private KeyguardUserSwitcherListView mListView; + private LinearLayout mEndGuestButton; + + // State info for the user switcher + private boolean mUserSwitcherOpen; + private int mCurrentUserId = UserHandle.USER_NULL; + private boolean mCurrentUserIsGuest; + private int mBarState; + private float mDarkAmount; + + private final KeyguardUpdateMonitorCallback mInfoCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onKeyguardVisibilityChanged(boolean showing) { + if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing)); + // Any time the keyguard is hidden, try to close the user switcher menu to + // restore keyguard to the default state + if (!showing) { + closeSwitcherIfOpenAndNotSimple(false); + } + } + + @Override + public void onUserSwitching(int userId) { + closeSwitcherIfOpenAndNotSimple(false); + } + }; + + private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { + @Override + public void onScreenTurnedOff() { + if (DEBUG) Log.d(TAG, "onScreenTurnedOff"); + closeSwitcherIfOpenAndNotSimple(false); + } + }; + + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStateChanged(int newState) { + if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState)); + + boolean goingToFullShade = mStatusBarStateController.goingToFullShade(); + boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway(); + int oldState = mBarState; + mBarState = newState; + + if (mStatusBarStateController.goingToFullShade() + || mKeyguardStateController.isKeyguardFadingAway()) { + closeSwitcherIfOpenAndNotSimple(true); + } + + setKeyguardUserSwitcherVisibility( + newState, + keyguardFadingAway, + goingToFullShade, + oldState); + } + + @Override + public void onDozeAmountChanged(float linearAmount, float amount) { + if (DEBUG) { + Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f", + linearAmount, amount)); + } + setDarkAmount(amount); + } + }; + + @Inject + public KeyguardUserSwitcherController( + KeyguardUserSwitcherView keyguardUserSwitcherView, + Context context, + @Main Resources resources, + LayoutInflater layoutInflater, + ScreenLifecycle screenLifecycle, + UserSwitcherController userSwitcherController, + KeyguardStateController keyguardStateController, + SysuiStatusBarStateController statusBarStateController, + KeyguardUpdateMonitor keyguardUpdateMonitor, + DozeParameters dozeParameters) { + super(keyguardUserSwitcherView); + if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController"); + mContext = context; + mScreenLifecycle = screenLifecycle; + mUserSwitcherController = userSwitcherController; + mKeyguardStateController = keyguardStateController; + mStatusBarStateController = statusBarStateController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater, + mUserSwitcherController, this); + mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, + keyguardStateController, dozeParameters); + } + + @Override + protected void onInit() { + super.onInit(); + + if (DEBUG) Log.d(TAG, "onInit"); + + mListView = mView.findViewById(R.id.keyguard_user_switcher_list); + mEndGuestButton = mView.findViewById(R.id.end_guest_button); + + mEndGuestButton.setOnClickListener(v -> { + mUserSwitcherController.showExitGuestDialog(mCurrentUserId); + }); + + mView.setOnTouchListener((v, event) -> { + if (!isListAnimating()) { + // Hide switcher if it didn't handle the touch event (and block the event from + // going through). + return closeSwitcherIfOpenAndNotSimple(true); + } + return false; + }); + } + + @Override + protected void onViewAttached() { + if (DEBUG) Log.d(TAG, "onViewAttached"); + mAdapter.registerDataSetObserver(mDataSetObserver); + mDataSetObserver.onChanged(); + mKeyguardUpdateMonitor.registerCallback(mInfoCallback); + mStatusBarStateController.addCallback(mStatusBarStateListener); + mScreenLifecycle.addObserver(mScreenObserver); + } + + @Override + protected void onViewDetached() { + if (DEBUG) Log.d(TAG, "onViewDetached"); + + // Detaching the view will always close the switcher + closeSwitcherIfOpenAndNotSimple(false); + + mAdapter.unregisterDataSetObserver(mDataSetObserver); + mKeyguardUpdateMonitor.removeCallback(mInfoCallback); + mStatusBarStateController.removeCallback(mStatusBarStateListener); + mScreenLifecycle.removeObserver(mScreenObserver); + } + + /** + * See: + * + * <ul> + * <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li> + * <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li> + * </ul> + * + * @return true if the user switcher should be open by default on the lock screen. + * @see android.os.UserManager#isUserSwitcherEnabled() + */ + public boolean isSimpleUserSwitcher() { + return mUserSwitcherController.isSimpleUserSwitcher(); + } + + /** + * @param animate if the transition should be animated + * @return true if the switcher state changed + */ + public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) { + if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) { + setUserSwitcherOpened(false /* open */, animate); + return true; + } + return false; + } + + public final DataSetObserver mDataSetObserver = new DataSetObserver() { + @Override + public void onChanged() { + refreshUserList(); + } + }; + + void refreshUserList() { + final int childCount = mListView.getChildCount(); + final int adapterCount = mAdapter.getCount(); + final int count = Math.max(childCount, adapterCount); + + if (DEBUG) { + Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount, + adapterCount)); + } + + boolean foundCurrentUser = false; + for (int i = 0; i < count; i++) { + if (i < adapterCount) { + View oldView = null; + if (i < childCount) { + oldView = mListView.getChildAt(i); + } + KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView) + mAdapter.getView(i, oldView, mListView); + UserSwitcherController.UserRecord userTag = + (UserSwitcherController.UserRecord) newView.getTag(); + if (userTag.isCurrent) { + if (i != 0) { + Log.w(TAG, "Current user is not the first view in the list"); + } + foundCurrentUser = true; + mCurrentUserId = userTag.info.id; + mCurrentUserIsGuest = userTag.isGuest; + // Current user is always visible + newView.updateVisibilities(true /* showItem */, + mUserSwitcherOpen /* showTextName */, false /* animate */); + } else { + // Views for non-current users are always expanded (e.g. they should the name + // next to the user icon). However, they could be hidden entirely if the list + // is closed. + newView.updateVisibilities(mUserSwitcherOpen /* showItem */, + true /* showTextName */, false /* animate */); + } + newView.setDarkAmount(mDarkAmount); + if (oldView == null) { + // We ran out of existing views. Add it at the end. + mListView.addView(newView); + } else if (oldView != newView) { + // We couldn't rebind the view. Replace it. + mListView.replaceView(newView, i); + } + } else { + mListView.removeLastView(); + } + } + if (!foundCurrentUser) { + Log.w(TAG, "Current user is not listed"); + mCurrentUserId = UserHandle.USER_NULL; + mCurrentUserIsGuest = false; + } + } + + /** + * Get the height of the keyguard user switcher view when closed. + */ + public int getUserIconHeight() { + View firstChild = mListView.getChildAt(0); + return firstChild == null ? 0 : firstChild.getHeight(); + } + + /** + * Set the visibility of the keyguard user switcher view based on some new state. + */ + public void setKeyguardUserSwitcherVisibility( + int statusBarState, + boolean keyguardFadingAway, + boolean goingToFullShade, + int oldStatusBarState) { + mKeyguardVisibilityHelper.setViewVisibility( + statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); + } + + /** + * Update position of the view with an optional animation + */ + public void updatePosition(int x, int y, boolean animate) { + PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, + animate); + PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), + ANIMATION_PROPERTIES, animate); + } + + /** + * Set keyguard user switcher view alpha. + */ + public void setAlpha(float alpha) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { + mView.setAlpha(alpha); + } + } + + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ + private void setDarkAmount(float darkAmount) { + boolean isAwake = darkAmount != 0; + if (darkAmount == mDarkAmount) { + return; + } + mDarkAmount = darkAmount; + mListView.setDarkAmount(darkAmount); + mView.setVisibility(isAwake ? View.VISIBLE : View.GONE); + if (!isAwake) { + closeSwitcherIfOpenAndNotSimple(false); + } + } + + private boolean isListAnimating() { + return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating(); + } + + /** + * Remove the callback if it exists. + */ + public void removeCallback() { + if (DEBUG) Log.d(TAG, "removeCallback"); + mKeyguardUserSwitcherCallback = null; + } + + /** + * Register to receive notifications about keyguard user switcher state + * (see {@link KeyguardUserSwitcherListener}. + * + * Only one callback can be used at a time. + * + * @param callback The callback to register + */ + public void setCallback(KeyguardUserSwitcherListener callback) { + if (DEBUG) Log.d(TAG, "setCallback"); + mKeyguardUserSwitcherCallback = new WeakReference<>(callback); + } + + /** + * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}. + * Switcher state is updatd before animations finish. + * + * @param animate true to animate transition. The user switcher state (i.e. + * {@link #isUserSwitcherOpen()}) is updated before animation is finished. + */ + private void setUserSwitcherOpened(boolean open, boolean animate) { + boolean wasOpen = mUserSwitcherOpen; + if (DEBUG) { + Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen, + open, animate)); + } + mUserSwitcherOpen = open; + if (mUserSwitcherOpen != wasOpen) { + notifyUserSwitcherStateChanged(); + } + updateVisibilities(animate); + } + + private void updateVisibilities(boolean animate) { + if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate)); + mEndGuestButton.animate().cancel(); + if (mUserSwitcherOpen && mCurrentUserIsGuest) { + // Show the "End guest session" button + mEndGuestButton.setVisibility(View.VISIBLE); + if (animate) { + mEndGuestButton.setAlpha(0f); + mEndGuestButton.animate() + .alpha(1f) + .setDuration(360) + .setInterpolator(Interpolators.ALPHA_IN) + .withEndAction(() -> { + mEndGuestButton.setClickable(true); + }); + } else { + mEndGuestButton.setClickable(true); + mEndGuestButton.setAlpha(1f); + } + } else { + // Hide the "End guest session" button. If it's already GONE, don't try to + // animate it or it will appear again for an instant. + mEndGuestButton.setClickable(false); + if (animate && mEndGuestButton.getVisibility() != View.GONE) { + mEndGuestButton.setVisibility(View.VISIBLE); + mEndGuestButton.setAlpha(1f); + mEndGuestButton.animate() + .alpha(0f) + .setDuration(360) + .setInterpolator(Interpolators.ALPHA_OUT) + .withEndAction(() -> { + mEndGuestButton.setVisibility(View.GONE); + mEndGuestButton.setAlpha(1f); + }); + } else { + mEndGuestButton.setVisibility(View.GONE); + mEndGuestButton.setAlpha(1f); + } + } + + mListView.updateVisibilities(mUserSwitcherOpen, animate); + } + + private boolean isUserSwitcherOpen() { + return mUserSwitcherOpen; + } + + private void notifyUserSwitcherStateChanged() { + if (DEBUG) { + Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b", + mUserSwitcherOpen)); + } + if (mKeyguardUserSwitcherCallback != null) { + KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get(); + if (cb != null) { + cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen); + } + } + } + + /** + * Callback for keyguard user switcher state information + */ + public interface KeyguardUserSwitcherListener { + + /** + * Called when the keyguard enters or leaves user switcher mode. This will be called + * before the animations are finished. + * + * @param open if true, keyguard is showing the user switcher or transitioning from/to user + * switcher mode. + */ + void onKeyguardUserSwitcherChanged(boolean open); + } + + static class KeyguardUserAdapter extends + UserSwitcherController.BaseUserAdapter implements View.OnClickListener { + + private final Context mContext; + private final Resources mResources; + private final LayoutInflater mLayoutInflater; + private KeyguardUserSwitcherController mKeyguardUserSwitcherController; + private View mCurrentUserView; + // List of users where the first entry is always the current user + private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>(); + + KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater, + UserSwitcherController controller, + KeyguardUserSwitcherController keyguardUserSwitcherController) { + super(controller); + mContext = context; + mResources = resources; + mLayoutInflater = layoutInflater; + mKeyguardUserSwitcherController = keyguardUserSwitcherController; + } + + @Override + public void notifyDataSetChanged() { + // At this point, value of isSimpleUserSwitcher() may have changed in addition to the + // data set + refreshUserOrder(); + super.notifyDataSetChanged(); + } + + void refreshUserOrder() { + ArrayList<UserSwitcherController.UserRecord> users = super.getUsers(); + mUsersOrdered = new ArrayList<>(users.size()); + for (int i = 0; i < users.size(); i++) { + UserSwitcherController.UserRecord record = users.get(i); + if (record.isCurrent) { + mUsersOrdered.add(0, record); + } else { + mUsersOrdered.add(record); + } + } + } + + @Override + protected ArrayList<UserSwitcherController.UserRecord> getUsers() { + return mUsersOrdered; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + UserSwitcherController.UserRecord item = getItem(position); + return createUserDetailItemView(convertView, parent, item); + } + + @Override + public String getName(Context context, UserSwitcherController.UserRecord item) { + if (item.isGuest) { + return context.getString(com.android.settingslib.R.string.guest_nickname); + } else { + return super.getName(context, item); + } + } + + KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) { + if (!(convertView instanceof KeyguardUserDetailItemView) + || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) { + convertView = mLayoutInflater.inflate( + R.layout.keyguard_user_switcher_item, parent, false); + } + return (KeyguardUserDetailItemView) convertView; + } + + KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent, + UserSwitcherController.UserRecord item) { + KeyguardUserDetailItemView v = convertOrInflate(convertView, parent); + v.setOnClickListener(this); + + String name = getName(mContext, item); + if (item.picture == null) { + v.bind(name, getDrawable(item).mutate(), item.resolveId()); + } else { + int avatarSize = + (int) mResources.getDimension(R.dimen.kg_framed_avatar_size); + Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize); + drawable.setColorFilter( + item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter()); + v.bind(name, drawable, item.info.id); + } + v.setActivated(item.isCurrent); + v.setDisabledByAdmin(item.isDisabledByAdmin); + v.setEnabled(item.isSwitchToEnabled); + v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); + + if (item.isCurrent) { + mCurrentUserView = v; + } + v.setTag(item); + return v; + } + + private Drawable getDrawable(UserSwitcherController.UserRecord item) { + Drawable drawable; + if (item.isCurrent && item.isGuest) { + drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); + } else { + drawable = getIconDrawable(mContext, item); + } + + int iconColorRes; + if (item.isSwitchToEnabled) { + iconColorRes = R.color.kg_user_switcher_avatar_icon_color; + } else { + iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color; + } + drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); + + Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar); + drawable = new LayerDrawable(new Drawable[]{bg, drawable}); + return drawable; + } + + @Override + public void onClick(View v) { + UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag(); + + if (mKeyguardUserSwitcherController.isListAnimating()) { + return; + } + + if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) { + if (user.isCurrent) { + // Close the switcher if tapping the current user + mKeyguardUserSwitcherController.setUserSwitcherOpened( + false /* open */, true /* animate */); + } else if (user.isSwitchToEnabled) { + if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) { + if (mCurrentUserView != null) { + mCurrentUserView.setActivated(false); + } + v.setActivated(true); + } + onUserListItemClicked(user); + } + } else { + // If switcher is closed, tapping anywhere in the view will open it + mKeyguardUserSwitcherController.setUserSwitcherOpened( + true /* open */, true /* animate */); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java new file mode 100644 index 000000000000..7c82c116eb3d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.android.keyguard.AlphaOptimizedLinearLayout; +import com.android.keyguard.KeyguardConstants; +import com.android.settingslib.animation.AppearAnimationUtils; +import com.android.settingslib.animation.DisappearAnimationUtils; +import com.android.systemui.Interpolators; + +/** + * The container for the user switcher on Keyguard. + */ +public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout { + + private static final String TAG = "KeyguardUserSwitcherListView"; + private static final boolean DEBUG = KeyguardConstants.DEBUG; + + private static final int ANIMATION_DURATION_OPENING = 360; + private static final int ANIMATION_DURATION_CLOSING = 240; + + private boolean mAnimating; + private final AppearAnimationUtils mAppearAnimationUtils; + private final DisappearAnimationUtils mDisappearAnimationUtils; + + public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) { + super(context, attrs); + setClipChildren(false); + mAppearAnimationUtils = new AppearAnimationUtils(context, ANIMATION_DURATION_OPENING, + -0.5f, 0.5f, Interpolators.FAST_OUT_SLOW_IN); + mDisappearAnimationUtils = new DisappearAnimationUtils(context, ANIMATION_DURATION_CLOSING, + 0.5f, 0.5f, Interpolators.FAST_OUT_LINEAR_IN); + } + + /** + * Set the amount (ratio) that the device has transitioned to doze. + * + * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. + */ + void setDarkAmount(float darkAmount) { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + if (v instanceof KeyguardUserDetailItemView) { + ((KeyguardUserDetailItemView) v).setDarkAmount(darkAmount); + } + } + } + + boolean isAnimating() { + return mAnimating; + } + + /** + * Update visibilities of this view and child views for when the user list is open or closed. + * If closed, this hides everything but the first item (which is always the current user). + */ + void updateVisibilities(boolean open, boolean animate) { + if (DEBUG) { + Log.d(TAG, String.format("updateVisibilities: open=%b animate=%b childCount=%d", + open, animate, getChildCount())); + } + + mAnimating = false; + + int userListCount = getChildCount(); + if (userListCount > 0) { + // The first child is always the current user. + KeyguardUserDetailItemView currentUserView = ((KeyguardUserDetailItemView) getChildAt( + 0)); + currentUserView.updateVisibilities(true /* showItem */, open /* showTextName */, + animate); + currentUserView.setClickable(true); + currentUserView.clearAnimation(); + } + + if (userListCount <= 1) { + return; + } + + if (animate) { + // Create an array of all the remaining users (that aren't the current user). + KeyguardUserDetailItemView[] otherUserViews = + new KeyguardUserDetailItemView[userListCount - 1]; + for (int i = 1, n = 0; i < userListCount; i++, n++) { + otherUserViews[n] = (KeyguardUserDetailItemView) getChildAt(i); + + // Update clickable state immediately so that the menu feels more responsive + otherUserViews[n].setClickable(open); + + // Before running the animation, ensure visibility is set correctly + otherUserViews[n].updateVisibilities( + true /* showItem */, true /* showTextName */, false /* animate */); + otherUserViews[n].clearAnimation(); + } + + setClipChildren(false); + setClipToPadding(false); + + mAnimating = true; + + final int nonCurrentUserCount = otherUserViews.length; + if (open) { + mAppearAnimationUtils.startAnimation(otherUserViews, () -> { + setClipChildren(true); + setClipToPadding(true); + mAnimating = false; + }); + } else { + mDisappearAnimationUtils.startAnimation(otherUserViews, () -> { + setClipChildren(true); + setClipToPadding(true); + for (int i = 0; i < nonCurrentUserCount; i++) { + otherUserViews[i].updateVisibilities( + false /* showItem */, true /* showTextName */, false /* animate */); + } + mAnimating = false; + }); + } + } else { + for (int i = 1; i < userListCount; i++) { + KeyguardUserDetailItemView nonCurrentUserView = + ((KeyguardUserDetailItemView) getChildAt(i)); + nonCurrentUserView.clearAnimation(); + nonCurrentUserView.updateVisibilities( + open /* showItem */, true /* showTextName */, false /* animate */); + nonCurrentUserView.setClickable(open); + } + } + } + + /** + * Replaces the view at the specified position in the group. + * + * @param index the position in the group of the view to remove + */ + void replaceView(KeyguardUserDetailItemView newView, int index) { + removeViewAt(index); + addView(newView, index); + } + + /** + * Removes the last view in the group. + */ + void removeLastView() { + int lastIndex = getChildCount() - 1; + removeViewAt(lastIndex); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java new file mode 100644 index 000000000000..3f0e23f7c72e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * The container for the user switcher on Keyguard. + */ +public class KeyguardUserSwitcherView extends FrameLayout { + + public KeyguardUserSwitcherView(Context context, AttributeSet attrs) { + super(context, attrs); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index 0fe338ea118d..1ab7652d4280 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -26,6 +26,7 @@ import android.net.NetworkCapabilities; import android.os.Handler; import android.os.Looper; import android.provider.Settings.Global; +import android.telephony.AccessNetworkConstants; import android.telephony.CellSignalStrength; import android.telephony.CellSignalStrengthCdma; import android.telephony.ServiceState; @@ -34,12 +35,18 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; +import android.telephony.ims.ImsException; +import android.telephony.ims.ImsMmTelManager; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; +import android.telephony.ims.RegistrationManager.RegistrationCallback; import android.text.Html; import android.text.TextUtils; import android.util.FeatureFlagUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.AccessibilityContentDescriptions; import com.android.settingslib.SignalIcon.MobileIconGroup; import com.android.settingslib.SignalIcon.MobileState; import com.android.settingslib.Utils; @@ -65,13 +72,19 @@ import java.util.Map; */ public class MobileSignalController extends SignalController<MobileState, MobileIconGroup> { private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - + private static final int STATUS_HISTORY_SIZE = 64; + private static final int IMS_TYPE_WWAN = 1; + private static final int IMS_TYPE_WLAN = 2; + private static final int IMS_TYPE_WLAN_CROSS_SIM = 3; private final TelephonyManager mPhone; + private final ImsMmTelManager mImsMmTelManager; private final SubscriptionDefaults mDefaults; private final String mNetworkNameDefault; private final String mNetworkNameSeparator; private final ContentObserver mObserver; private final boolean mProviderModel; + private final Handler mReceiverHandler; + private int mImsType = IMS_TYPE_WWAN; // Save entire info for logging, we only use the id. final SubscriptionInfo mSubscriptionInfo; // @VisibleForDemoMode @@ -86,16 +99,21 @@ public class MobileSignalController extends SignalController<MobileState, Mobile TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE); private ServiceState mServiceState; private SignalStrength mSignalStrength; + private int mLastLevel; private MobileIconGroup mDefaultIcons; private Config mConfig; @VisibleForTesting boolean mInflateSignalStrengths = false; private MobileStatusTracker.Callback mCallback; + private RegistrationCallback mRegistrationCallback; + private int mLastWwanLevel; + private int mLastWlanLevel; + private int mLastWlanCrossSimLevel; @VisibleForTesting MobileStatusTracker mMobileStatusTracker; - // Save the previous HISTORY_SIZE states for logging. - private final String[] mMobileStatusHistory = new String[HISTORY_SIZE]; + // Save the previous STATUS_HISTORY_SIZE states for logging. + private final String[] mMobileStatusHistory = new String[STATUS_HISTORY_SIZE]; // Where to copy the next state into. private int mMobileStatusHistoryIndex; @@ -116,6 +134,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile .toString(); mNetworkNameDefault = getTextIfExists( com.android.internal.R.string.lockscreen_carrier_default).toString(); + mReceiverHandler = new Handler(receiverLooper); mNetworkToIconLookup = mapIconSets(mConfig); mDefaultIcons = getDefaultIcons(mConfig); @@ -133,6 +152,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } }; mCallback = new MobileStatusTracker.Callback() { + private String mLastStatus; + @Override public void onMobileStatusChanged(boolean updateTelephony, MobileStatus mobileStatus) { @@ -141,11 +162,15 @@ public class MobileSignalController extends SignalController<MobileState, Mobile + " updateTelephony=" + updateTelephony + " mobileStatus=" + mobileStatus.toString()); } - String status = new StringBuilder() - .append(SSDF.format(System.currentTimeMillis())).append(",") - .append(mobileStatus.toString()) - .toString(); - recordLastMobileStatus(status); + String currentStatus = mobileStatus.toString(); + if (!currentStatus.equals(mLastStatus)) { + mLastStatus = currentStatus; + String status = new StringBuilder() + .append(SSDF.format(System.currentTimeMillis())).append(",") + .append(currentStatus) + .toString(); + recordLastMobileStatus(status); + } updateMobileStatus(mobileStatus); if (updateTelephony) { updateTelephony(); @@ -154,6 +179,53 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } } }; + + mRegistrationCallback = new RegistrationCallback() { + @Override + public void onRegistered(ImsRegistrationAttributes attributes) { + Log.d(mTag, "onRegistered: " + "attributes=" + attributes); + int imsTransportType = attributes.getTransportType(); + int registrationAttributes = attributes.getAttributeFlags(); + if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { + mImsType = IMS_TYPE_WWAN; + IconState statusIcon = new IconState( + true, + getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false), + getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false)); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { + if (registrationAttributes == 0) { + mImsType = IMS_TYPE_WLAN; + IconState statusIcon = new IconState( + true, + getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true), + getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true)); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } else if (registrationAttributes + == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) { + mImsType = IMS_TYPE_WLAN_CROSS_SIM; + IconState statusIcon = new IconState( + true, + getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false), + getCallStrengthDescription( + mLastWlanCrossSimLevel, /* isWifi= */false)); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } + } + } + + @Override + public void onUnregistered(ImsReasonInfo info) { + Log.d(mTag, "onDeregistered: " + "info=" + info); + mImsType = IMS_TYPE_WWAN; + IconState statusIcon = new IconState( + true, + getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false), + getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false)); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } + }; + mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId()); mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper, info, mDefaults, mCallback); mProviderModel = FeatureFlagUtils.isEnabled( @@ -202,14 +274,41 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mContext.getContentResolver().registerContentObserver(Global.getUriFor( Global.MOBILE_DATA + mSubscriptionInfo.getSubscriptionId()), true, mObserver); + if (mProviderModel) { + mReceiverHandler.post(mTryRegisterIms); + } } + // There is no listener to monitor whether the IMS service is ready, so we have to retry the + // IMS registration. + private final Runnable mTryRegisterIms = new Runnable() { + private static final int MAX_RETRY = 12; + private int mRetryCount; + + @Override + public void run() { + try { + mRetryCount++; + mImsMmTelManager.registerImsRegistrationCallback( + mReceiverHandler::post, mRegistrationCallback); + Log.d(mTag, "registerImsRegistrationCallback succeeded"); + } catch (RuntimeException | ImsException e) { + if (mRetryCount < MAX_RETRY) { + Log.e(mTag, mRetryCount + " registerImsRegistrationCallback failed", e); + // Wait for 5 seconds to retry + mReceiverHandler.postDelayed(mTryRegisterIms, 5000); + } + } + } + }; + /** * Stop listening for phone state changes. */ public void unregisterListener() { mMobileStatusTracker.setListening(false); mContext.getContentResolver().unregisterContentObserver(mObserver); + mImsMmTelManager.unregisterImsRegistrationCallback(mRegistrationCallback); } private void updateInflateSignalStrength() { @@ -452,9 +551,9 @@ public class MobileSignalController extends SignalController<MobileState, Mobile /** * Extracts the CellSignalStrengthCdma from SignalStrength then returns the level */ - private final int getCdmaLevel() { + private int getCdmaLevel(SignalStrength signalStrength) { List<CellSignalStrengthCdma> signalStrengthCdma = - mSignalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class); + signalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class); if (!signalStrengthCdma.isEmpty()) { return signalStrengthCdma.get(0).getLevel(); } @@ -467,6 +566,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mCurrentState.dataSim = mobileStatus.dataSim; mCurrentState.carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode; mDataState = mobileStatus.dataState; + notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength); mSignalStrength = mobileStatus.signalStrength; mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo; int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1; @@ -481,9 +581,117 @@ public class MobileSignalController extends SignalController<MobileState, Mobile && (lastVoiceState == -1 || (lastVoiceState == ServiceState.STATE_IN_SERVICE || currentVoiceState == ServiceState.STATE_IN_SERVICE))) { - notifyNoCallingStatusChange( - currentVoiceState != ServiceState.STATE_IN_SERVICE, - mSubscriptionInfo.getSubscriptionId()); + boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE; + IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms, + getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString()); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } + } + + private int getCallStrengthIcon(int level, boolean isWifi) { + return isWifi ? TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[level] + : TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[level]; + } + + private String getCallStrengthDescription(int level, boolean isWifi) { + return isWifi + ? getTextIfExists(AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[level]) + .toString() + : getTextIfExists(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[level]) + .toString(); + } + + void refreshCallIndicator(SignalCallback callback) { + boolean isNoCalling = mServiceState != null + && mServiceState.getState() != ServiceState.STATE_IN_SERVICE; + IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms, + getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString()); + callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId()); + + switch (mImsType) { + case IMS_TYPE_WWAN: + statusIcon = new IconState( + true, + getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false), + getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false)); + break; + case IMS_TYPE_WLAN: + statusIcon = new IconState( + true, + getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true), + getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true)); + break; + case IMS_TYPE_WLAN_CROSS_SIM: + statusIcon = new IconState( + true, + getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false), + getCallStrengthDescription(mLastWlanCrossSimLevel, /* isWifi= */false)); + } + callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } + + void notifyWifiLevelChange(int level) { + if (!mProviderModel) { + return; + } + Log.d("mTag", "notifyWifiLevelChange " + mImsType); + mLastWlanLevel = level; + if (mImsType != IMS_TYPE_WLAN) { + return; + } + IconState statusIcon = new IconState( + true, + getCallStrengthIcon(level, /* isWifi= */true), + getCallStrengthDescription(level, /* isWifi= */true)); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } + + void notifyDefaultMobileLevelChange(int level) { + if (!mProviderModel) { + return; + } + Log.d("mTag", "notifyDefaultMobileLevelChange " + mImsType); + mLastWlanCrossSimLevel = level; + if (mImsType != IMS_TYPE_WLAN_CROSS_SIM) { + return; + } + IconState statusIcon = new IconState( + true, + getCallStrengthIcon(level, /* isWifi= */false), + getCallStrengthDescription(level, /* isWifi= */false)); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } + + void notifyMobileLevelChangeIfNecessary(SignalStrength signalStrength) { + if (!mProviderModel) { + return; + } + int newLevel = getSignalLevel(signalStrength); + if (newLevel != mLastLevel) { + mLastLevel = newLevel; + Log.d("mTag", "notifyMobileLevelChangeIfNecessary " + mImsType); + mLastWwanLevel = newLevel; + if (mImsType == IMS_TYPE_WWAN) { + IconState statusIcon = new IconState( + true, + getCallStrengthIcon(newLevel, /* isWifi= */false), + getCallStrengthDescription(newLevel, /* isWifi= */false)); + notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId()); + } + if (mCurrentState.dataSim) { + mNetworkController.notifyDefaultMobileLevelChange(newLevel); + } + } + } + + int getSignalLevel(SignalStrength signalStrength) { + if (signalStrength == null) { + return 0; + } + if (!signalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) { + return getCdmaLevel(signalStrength); + } else { + return signalStrength.getLevel(); } } @@ -501,11 +709,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile checkDefaultData(); mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null; if (mCurrentState.connected) { - if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) { - mCurrentState.level = getCdmaLevel(); - } else { - mCurrentState.level = mSignalStrength.getLevel(); - } + mCurrentState.level = getSignalLevel(mSignalStrength); } String iconKey = getIconKey(mTelephonyDisplayInfo); @@ -577,7 +781,13 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } private void recordLastMobileStatus(String mobileStatus) { - mMobileStatusHistory[mMobileStatusHistoryIndex++ & (HISTORY_SIZE - 1)] = mobileStatus; + mMobileStatusHistory[mMobileStatusHistoryIndex] = mobileStatus; + mMobileStatusHistoryIndex = (mMobileStatusHistoryIndex + 1) % STATUS_HISTORY_SIZE; + } + + @VisibleForTesting + void setImsType(int imsType) { + mImsType = imsType; } @Override @@ -592,15 +802,17 @@ public class MobileSignalController extends SignalController<MobileState, Mobile pw.println(" isDataDisabled=" + isDataDisabled() + ","); pw.println(" MobileStatusHistory"); int size = 0; - for (int i = 0; i < HISTORY_SIZE; i++) { - if (mMobileStatusHistory[i] != null) size++; + for (int i = 0; i < STATUS_HISTORY_SIZE; i++) { + if (mMobileStatusHistory[i] != null) { + size++; + } } // Print out the previous states in ordered number. - for (int i = mMobileStatusHistoryIndex + HISTORY_SIZE - 1; - i >= mMobileStatusHistoryIndex + HISTORY_SIZE - size; i--) { + for (int i = mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - 1; + i >= mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - size; i--) { pw.println(" Previous MobileStatus(" - + (mMobileStatusHistoryIndex + HISTORY_SIZE - i) + "): " - + mMobileStatusHistory[i & (HISTORY_SIZE - 1)]); + + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): " + + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index e60d5c5f2fa8..0a9fead9cb64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -53,7 +53,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D /** * Callback for listeners to be able to update the state of any UI tracking connectivity - * @param statusIcon the icon that should be shown in the status bar + * @param statusIcon the icon that should be shown in the status bar * @param qsIcon the icon to show in Quick Settings * @param statusType the resId of the data type icon (e.g. LTE) to show in the status bar * @param qsType similar to above, the resId of the data type icon to show in Quick Settings @@ -95,11 +95,11 @@ public interface NetworkController extends CallbackController<SignalCallback>, D boolean noNetworksAvailable) {} /** - * Callback for listeners to be able to update the no calling & SMS status - * @param noCalling whether the calling and SMS is not working. + * Callback for listeners to be able to update the call indicator + * @param statusIcon the icon for the call indicator * @param subId subscription ID for which to update the UI */ - default void setNoCallingStatus(boolean noCalling, int subId) {} + default void setCallIndicator(IconState statusIcon, int subId) {} } public interface EmergencyListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 80c78115f7bd..9f921429f7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -554,6 +554,20 @@ public class NetworkControllerImpl extends BroadcastReceiver return controller != null ? controller.getNetworkNameForCarrierWiFi() : ""; } + void notifyWifiLevelChange(int level) { + for (int i = 0; i < mMobileSignalControllers.size(); i++) { + MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i); + mobileSignalController.notifyWifiLevelChange(level); + } + } + + void notifyDefaultMobileLevelChange(int level) { + for (int i = 0; i < mMobileSignalControllers.size(); i++) { + MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i); + mobileSignalController.notifyDefaultMobileLevelChange(level); + } + } + private void notifyControllersMobileDataChanged() { for (int i = 0; i < mMobileSignalControllers.size(); i++) { MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i); @@ -623,6 +637,9 @@ public class NetworkControllerImpl extends BroadcastReceiver for (int i = 0; i < mMobileSignalControllers.size(); i++) { MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i); mobileSignalController.notifyListeners(cb); + if (mProviderModel) { + mobileSignalController.refreshCallIndicator(cb); + } } mCallbackHandler.setListening(cb, true); } @@ -1272,7 +1289,8 @@ public class NetworkControllerImpl extends BroadcastReceiver } private void recordLastNetworkCallback(String callback) { - mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback; + mHistory[mHistoryIndex] = callback; + mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; } private SubscriptionInfo addSignalController(int id, int simSlotIndex) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java index 554145e9773e..4b6722c17b85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java @@ -23,6 +23,7 @@ import android.util.Log; import com.android.settingslib.SignalIcon.IconGroup; import com.android.settingslib.SignalIcon.State; +import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import java.io.PrintWriter; @@ -167,8 +168,8 @@ public abstract class SignalController<T extends State, I extends IconGroup> { } } - protected final void notifyNoCallingStatusChange(boolean noCalling, int subId) { - mCallbackHandler.setNoCallingStatus(noCalling, subId); + protected final void notifyCallStateChange(IconState statusIcon, int subId) { + mCallbackHandler.setCallIndicator(statusIcon, subId); } /** @@ -187,7 +188,8 @@ public abstract class SignalController<T extends State, I extends IconGroup> { * and last value of any state data. */ protected void recordLastState() { - mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState); + mHistory[mHistoryIndex].copyFrom(mLastState); + mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; } public void dump(PrintWriter pw) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 68d74ef760b4..d4029e64036e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -121,6 +121,7 @@ public class UserSwitcherController implements Dumpable { private Intent mSecondaryUserServiceIntent; private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2); private final UiEventLogger mUiEventLogger; + public final DetailAdapter mUserDetailAdapter; @Inject public UserSwitcherController(Context context, KeyguardStateController keyguardStateController, @@ -131,6 +132,7 @@ public class UserSwitcherController implements Dumpable { mBroadcastDispatcher = broadcastDispatcher; mActivityTaskManager = activityTaskManager; mUiEventLogger = uiEventLogger; + mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger); if (!UserManager.isGuestUserEphemeral()) { mGuestResumeSessionReceiver.register(mBroadcastDispatcher); } @@ -423,7 +425,7 @@ public class UserSwitcherController implements Dumpable { } } - private void showExitGuestDialog(int id) { + protected void showExitGuestDialog(int id) { int newId = UserHandle.USER_SYSTEM; if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) { UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); @@ -680,11 +682,7 @@ public class UserSwitcherController implements Dumpable { if (item.isAddUser) { iconRes = R.drawable.ic_add_circle; } else if (item.isGuest) { - if (item.isCurrent) { - iconRes = R.drawable.ic_exit_to_app; - } else { - iconRes = R.drawable.ic_avatar_guest_user; - } + iconRes = R.drawable.ic_avatar_guest_user; } else { iconRes = R.drawable.ic_avatar_user; } @@ -785,9 +783,20 @@ public class UserSwitcherController implements Dumpable { } } - public final DetailAdapter userDetailAdapter = new DetailAdapter() { + public static class UserDetailAdapter implements DetailAdapter { private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS); + private final UserSwitcherController mUserSwitcherController; + private final Context mContext; + private final UiEventLogger mUiEventLogger; + + UserDetailAdapter(UserSwitcherController userSwitcherController, Context context, + UiEventLogger uiEventLogger) { + mUserSwitcherController = userSwitcherController; + mContext = context; + mUiEventLogger = uiEventLogger; + } + @Override public CharSequence getTitle() { return mContext.getString(R.string.quick_settings_user_title); @@ -798,7 +807,7 @@ public class UserSwitcherController implements Dumpable { UserDetailView v; if (!(convertView instanceof UserDetailView)) { v = UserDetailView.inflate(context, parent, false); - v.createAndSetAdapter(UserSwitcherController.this, mUiEventLogger); + v.createAndSetAdapter(mUserSwitcherController, mUiEventLogger); } else { v = (UserDetailView) convertView; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index 1fd2ccbf8500..16998d7be936 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -40,6 +40,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import java.io.PrintWriter; import java.util.Objects; public class WifiSignalController extends @@ -202,6 +203,7 @@ public class WifiSignalController extends mCurrentState.connected = mWifiTracker.connected; mCurrentState.ssid = mWifiTracker.ssid; mCurrentState.rssi = mWifiTracker.rssi; + notifyWifiLevelChangeIfNecessary(mWifiTracker.level); mCurrentState.level = mWifiTracker.level; mCurrentState.statusLabel = mWifiTracker.statusLabel; mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged; @@ -211,6 +213,12 @@ public class WifiSignalController extends : mUnmergedWifiIconGroup; } + void notifyWifiLevelChangeIfNecessary(int level) { + if (level != mCurrentState.level) { + mNetworkController.notifyWifiLevelChange(level); + } + } + boolean isCarrierMergedWifi(int subId) { return mCurrentState.isDefault && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId); @@ -225,6 +233,12 @@ public class WifiSignalController extends notifyListenersIfNecessary(); } + @Override + public void dump(PrintWriter pw) { + super.dump(pw); + mWifiTracker.dump(pw); + } + /** * Handler to receive the data activity on wifi. */ diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 78639147a375..9e78a664d35f 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -15,11 +15,11 @@ */ package com.android.systemui.theme; +import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.om.OverlayManager; import android.content.om.OverlayManagerTransaction; -import android.os.SystemProperties; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; @@ -53,13 +53,6 @@ import java.util.stream.Collectors; public class ThemeOverlayApplier implements Dumpable { private static final String TAG = "ThemeOverlayApplier"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean MONET_ENABLED = SystemProperties - .getBoolean("persist.sysui.monet", false); - - @VisibleForTesting - static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color"; - @VisibleForTesting - static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color"; @VisibleForTesting static final String ANDROID_PACKAGE = "android"; @@ -68,10 +61,8 @@ public class ThemeOverlayApplier implements Dumpable { @VisibleForTesting static final String SYSUI_PACKAGE = "com.android.systemui"; - @VisibleForTesting static final String OVERLAY_CATEGORY_ACCENT_COLOR = "android.theme.customization.accent_color"; - @VisibleForTesting static final String OVERLAY_CATEGORY_SYSTEM_PALETTE = "android.theme.customization.system_palette"; @VisibleForTesting @@ -120,16 +111,6 @@ public class ThemeOverlayApplier implements Dumpable { OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SYSUI); - /** - * List of main colors of Monet themes. These are extracted from overlays installed - * on the system. - */ - private final ArrayList<Integer> mMainSystemColors = new ArrayList<>(); - /** - * Same as above, but providing accent colors instead of a system palette. - */ - private final ArrayList<Integer> mAccentColors = new ArrayList<>(); - /* Allowed overlay categories for each target package. */ private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>(); /* Target package for each overlay category. */ @@ -165,64 +146,17 @@ public class ThemeOverlayApplier implements Dumpable { mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage); - collectMonetSystemOverlays(); dumpManager.registerDumpable(TAG, this); } /** - * List of accent colors available as Monet overlays. - */ - List<Integer> getAvailableAccentColors() { - return mAccentColors; - } - - /** - * List of main system colors available as Monet overlays. - */ - List<Integer> getAvailableSystemColors() { - return mMainSystemColors; - } - - private void collectMonetSystemOverlays() { - if (!MONET_ENABLED) { - return; - } - List<OverlayInfo> androidOverlays = mOverlayManager - .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM); - for (OverlayInfo overlayInfo : androidOverlays) { - String packageName = overlayInfo.packageName; - if (DEBUG) { - Log.d(TAG, "Processing overlay " + packageName); - } - if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category) - && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) { - try { - String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, ""); - mMainSystemColors.add(Integer.parseInt(color, 16)); - } catch (NumberFormatException e) { - Log.w(TAG, "Invalid package name for overlay " + packageName, e); - } - } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category) - && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) { - try { - String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, ""); - mAccentColors.add(Integer.parseInt(color, 16)); - } catch (NumberFormatException e) { - Log.w(TAG, "Invalid package name for overlay " + packageName, e); - } - } else if (DEBUG) { - Log.d(TAG, "Unknown overlay: " + packageName + " category: " - + overlayInfo.category); - } - } - } - - /** * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that * affect sysui will also be applied to the system user. */ void applyCurrentUserOverlays( - Map<String, String> categoryToPackage, Set<UserHandle> userHandles) { + Map<String, OverlayIdentifier> categoryToPackage, + FabricatedOverlay[] pendingCreation, + Set<UserHandle> userHandles) { // Disable all overlays that have not been specified in the user setting. final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); overlayCategoriesToDisable.removeAll(categoryToPackage.keySet()); @@ -241,11 +175,16 @@ public class ThemeOverlayApplier implements Dumpable { .collect(Collectors.toList()); OverlayManagerTransaction.Builder transaction = getTransactionBuilder(); + if (pendingCreation != null) { + for (FabricatedOverlay overlay : pendingCreation) { + transaction.registerFabricatedOverlay(overlay); + } + } + // Toggle overlays in the order of THEME_CATEGORIES. for (String category : THEME_CATEGORIES) { if (categoryToPackage.containsKey(category)) { - OverlayIdentifier overlayInfo = - new OverlayIdentifier(categoryToPackage.get(category)); + OverlayIdentifier overlayInfo = categoryToPackage.get(category); setEnabled(transaction, overlayInfo, category, userHandles, true); } } @@ -255,7 +194,11 @@ public class ThemeOverlayApplier implements Dumpable { } mExecutor.execute(() -> { - mOverlayManager.commit(transaction.build()); + try { + mOverlayManager.commit(transaction.build()); + } catch (SecurityException | IllegalStateException e) { + Log.e(TAG, "setEnabled failed", e); + } }); } @@ -284,7 +227,7 @@ public class ThemeOverlayApplier implements Dumpable { */ @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("mMainSystemColors=" + mMainSystemColors.size()); - pw.println("mAccentColors=" + mAccentColors.size()); + pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories); + pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage); } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index d9f474480bc9..522a42b8d4b4 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -18,6 +18,7 @@ package com.android.systemui.theme; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WallpaperColors; import android.app.WallpaperManager; @@ -25,6 +26,8 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.om.FabricatedOverlay; +import android.content.om.OverlayIdentifier; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Color; @@ -40,7 +43,6 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.ColorUtils; import com.android.systemui.Dumpable; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -48,6 +50,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; @@ -59,10 +62,10 @@ import org.json.JSONObject; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -77,9 +80,12 @@ import javax.inject.Inject; */ @SysUISingleton public class ThemeOverlayController extends SystemUI implements Dumpable { - private static final String TAG = "ThemeOverlayController"; + protected static final String TAG = "ThemeOverlayController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + protected static final int MAIN = 0; + protected static final int ACCENT = 1; + // If lock screen wallpaper colors should also be considered when selecting the theme. // Doing this has performance impact, given that overlays would need to be swapped when // the device unlocks. @@ -95,16 +101,19 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private final Handler mBgHandler; private final WallpaperManager mWallpaperManager; private final KeyguardStateController mKeyguardStateController; + private final boolean mIsMonetEnabled; private WallpaperColors mLockColors; private WallpaperColors mSystemColors; - // Color extracted from wallpaper, NOT the color used on the overlay + // If fabricated overlays were already created for the current theme. + private boolean mNeedsOverlayCreation; + // Dominant olor extracted from wallpaper, NOT the color used on the overlay protected int mMainWallpaperColor = Color.TRANSPARENT; - // Color extracted from wallpaper, NOT the color used on the overlay + // Accent color extracted from wallpaper, NOT the color used on the overlay protected int mWallpaperAccentColor = Color.TRANSPARENT; - // Main system color that maps to an overlay color - private int mSystemOverlayColor = Color.TRANSPARENT; - // Accent color that maps to an overlay color - private int mAccentOverlayColor = Color.TRANSPARENT; + // System colors overlay + private FabricatedOverlay mSystemOverlay; + // Accent colors overlay + private FabricatedOverlay mAccentOverlay; @Inject public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, @@ -112,9 +121,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier, SecureSettings secureSettings, WallpaperManager wallpaperManager, UserManager userManager, KeyguardStateController keyguardStateController, - DumpManager dumpManager) { + DumpManager dumpManager, FeatureFlags featureFlags) { super(context); + mIsMonetEnabled = featureFlags.isMonetEnabled(); mBroadcastDispatcher = broadcastDispatcher; mUserManager = userManager; mBgExecutor = bgExecutor; @@ -221,20 +231,16 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mMainWallpaperColor = mainColor; mWallpaperAccentColor = accentCandidate; - // Let's compare these colors to our finite set of overlays, and then pick an overlay. - List<Integer> systemColors = mThemeManager.getAvailableSystemColors(); - List<Integer> accentColors = mThemeManager.getAvailableAccentColors(); - - if (systemColors.size() == 0 || accentColors.size() == 0) { + if (mIsMonetEnabled) { + mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN); + mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT); + mNeedsOverlayCreation = true; if (DEBUG) { - Log.d(TAG, "Cannot apply system theme, palettes are unavailable"); + Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: " + + mAccentOverlay); } - return; } - mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor); - mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor); - updateThemeOverlays(); } @@ -257,42 +263,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } /** - * Given a color and a list of candidates, return the candidate that's the most similar to the - * given color. + * Given a color candidate, return an overlay definition. */ - protected int getClosest(List<Integer> candidates, int color) { - float[] hslMain = new float[3]; - float[] hslCandidate = new float[3]; - - ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain); - hslMain[0] /= 360f; - - // To close to white or black, let's use the default system theme instead of - // applying a colorized one. - if (hslMain[2] < 0.05 || hslMain[2] > 0.95) { - return Color.TRANSPARENT; - } - - float minDistance = Float.MAX_VALUE; - int closestColor = Color.TRANSPARENT; - for (int candidate: candidates) { - ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate), - hslCandidate); - hslCandidate[0] /= 360f; - - float sqDistance = squared(hslCandidate[0] - hslMain[0]) - + squared(hslCandidate[1] - hslMain[1]) - + squared(hslCandidate[2] - hslMain[2]); - if (sqDistance < minDistance) { - minDistance = sqDistance; - closestColor = candidate; - } - } - return closestColor; - } - - private static float squared(float f) { - return f * f; + protected @Nullable FabricatedOverlay getOverlay(int color, int type) { + return null; } private void updateThemeOverlays() { @@ -301,20 +275,15 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, currentUser); if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson); - boolean hasSystemPalette = false; - boolean hasAccentColor = false; - final Map<String, String> categoryToPackage = new ArrayMap<>(); + final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>(); if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); for (String category : ThemeOverlayApplier.THEME_CATEGORIES) { if (object.has(category)) { - if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) { - hasAccentColor = true; - } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) { - hasSystemPalette = true; - } - categoryToPackage.put(category, object.getString(category)); + OverlayIdentifier identifier = + new OverlayIdentifier(object.getString(category)); + categoryToPackage.put(category, identifier); } } } catch (JSONException e) { @@ -322,17 +291,41 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } } - // Let's apply the system palette, but only if it was not overridden by the style picker. - if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) { - categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, - ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE - + getColorString(mSystemOverlayColor)); + // Let's generate system overlay if the style picker decided to override it. + OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE); + if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) { + try { + int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); + mSystemOverlay = getOverlay(color, MAIN); + mNeedsOverlayCreation = true; + categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName()); + } + } + + // Same for accent color. + OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR); + if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) { + try { + int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); + mAccentOverlay = getOverlay(color, ACCENT); + mNeedsOverlayCreation = true; + categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName()); + } + } + + // Compatibility with legacy themes, where full packages were defined, instead of just + // colors. + if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE) + && mSystemOverlay != null) { + categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier()); } - // Same for the accent color - if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) { - categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, - ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE - + getColorString(mAccentOverlayColor)); + if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR) + && mAccentOverlay != null) { + categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier()); } Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); @@ -341,28 +334,31 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { userHandles.add(userInfo.getUserHandle()); } } - mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles); - } - - private String getColorString(int color) { - String colorString = Integer.toHexString(color).toUpperCase(); - while (colorString.length() < 6) { - colorString = "0" + colorString; + if (DEBUG) { + Log.d(TAG, "Applying overlays: " + categoryToPackage.keySet().stream() + .map(key -> key + " -> " + categoryToPackage.get(key)).collect( + Collectors.joining(", "))); } - // Remove alpha component - if (colorString.length() > 6) { - colorString = colorString.substring(colorString.length() - 6); + if (mNeedsOverlayCreation) { + mNeedsOverlayCreation = false; + mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { + mSystemOverlay, mAccentOverlay + }, userHandles); + } else { + mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles); } - return colorString; } @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER); pw.println("mLockColors=" + mLockColors); pw.println("mSystemColors=" + mSystemColors); pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor)); - pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor)); - pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor)); + pw.println("mSystemOverlayColor=" + mSystemOverlay); + pw.println("mAccentOverlayColor=" + mAccentOverlay); + pw.println("mIsMonetEnabled=" + mIsMonetEnabled); + pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation); } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 09335afc059c..b67574d1c4de 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -109,7 +109,7 @@ public abstract class TunerService { dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel), (OnClickListener) null); dialog.setButton(DialogInterface.BUTTON_POSITIVE, - context.getString(R.string.qs_customize_remove), new OnClickListener() { + context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Tell the tuner (in main SysUI process) to clear all its settings. diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index df54eabca8e7..181fdce11e11 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -33,7 +33,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Dialog; @@ -51,6 +55,9 @@ import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.RotateDrawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Debug; @@ -81,6 +88,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; @@ -88,6 +97,7 @@ import android.widget.Toast; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -99,6 +109,8 @@ import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.AlphaTintDrawableWrapper; +import com.android.systemui.util.RoundedCornerProgressDrawable; import java.io.PrintWriter; import java.util.ArrayList; @@ -124,8 +136,14 @@ public class VolumeDialogImpl implements VolumeDialog, static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; + private static final int DRAWER_ANIMATION_DURATION_SHORT = 175; + private static final int DRAWER_ANIMATION_DURATION = 250; + private final int mDialogShowAnimationDurationMs; private final int mDialogHideAnimationDurationMs; + private final int mRingerDrawerItemSize; + private final boolean mShowVibrate; + private final int mRingerCount; private final boolean mShowLowMediaVolumeIcon; private final boolean mChangeVolumeRowTintWhenInactive; @@ -140,6 +158,30 @@ public class VolumeDialogImpl implements VolumeDialog, private ViewGroup mDialogView; private ViewGroup mDialogRowsView; private ViewGroup mRinger; + + private ViewGroup mSelectedRingerContainer; + private ImageView mSelectedRingerIcon; + + private ViewGroup mRingerDrawerContainer; + private ViewGroup mRingerDrawerMute; + private ViewGroup mRingerDrawerVibrate; + private ViewGroup mRingerDrawerNormal; + private ImageView mRingerDrawerMuteIcon; + private ImageView mRingerDrawerVibrateIcon; + private ImageView mRingerDrawerNormalIcon; + + /** + * View that draws the 'selected' background behind one of the three ringer choices in the + * drawer. + */ + private ViewGroup mRingerDrawerNewSelectionBg; + + private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f); + private ImageView mRingerDrawerIconAnimatingSelected; + private ImageView mRingerDrawerIconAnimatingDeselected; + + private boolean mIsRingerDrawerOpen = false; + private ImageButton mRingerIcon; private ViewGroup mODICaptionsView; private CaptionsToggleImageButton mODICaptionsIcon; @@ -191,6 +233,12 @@ public class VolumeDialogImpl implements VolumeDialog, mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs); mDialogHideAnimationDurationMs = mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs); + mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize( + R.dimen.volume_ringer_drawer_item_size); + mShowVibrate = mController.hasVibrator(); + + // Normal, mute, and possibly vibrate. + mRingerCount = mShowVibrate ? 3 : 2; } @Override @@ -314,6 +362,20 @@ public class VolumeDialogImpl implements VolumeDialog, mZenIcon = mRinger.findViewById(R.id.dnd_icon); } + mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon); + mSelectedRingerContainer = mDialog.findViewById( + R.id.volume_new_ringer_active_icon_container); + + mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute); + mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal); + mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate); + mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon); + mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon); + mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); + mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); + + setupRingerDrawer(); + mODICaptionsView = mDialog.findViewById(R.id.odi_captions); if (mODICaptionsView != null) { mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); @@ -475,38 +537,273 @@ public class VolumeDialogImpl implements VolumeDialog, row.anim = null; + final LayerDrawable seekbarDrawable = + (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar); + + final LayerDrawable seekbarBgDrawable = + (LayerDrawable) seekbarDrawable.findDrawableByLayerId(android.R.id.background); + + row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_solid); + + row.sliderBgIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_icon)).getDrawable(); + + final LayerDrawable seekbarProgressDrawable = (LayerDrawable) + ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId( + android.R.id.progress)).getDrawable(); + + row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_solid); + + row.sliderProgressIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_icon)).getDrawable(); + + row.slider.setProgressDrawable(seekbarDrawable); + row.slider.setThumb(null); + row.icon = row.view.findViewById(R.id.volume_row_icon); - row.icon.setImageResource(iconRes); - if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { - row.icon.setOnClickListener(v -> { - Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); - mController.setActiveStream(row.stream); - if (row.stream == AudioManager.STREAM_RING) { - final boolean hasVibrator = mController.hasVibrator(); - if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { - if (hasVibrator) { - mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + + row.setIcon(iconRes); + + if (row.icon != null) { + if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { + row.icon.setOnClickListener(v -> { + Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); + mController.setActiveStream(row.stream); + if (row.stream == AudioManager.STREAM_RING) { + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + } else { + final boolean wasZero = row.ss.level == 0; + mController.setStreamVolume(stream, + wasZero ? row.lastAudibleLevel : 0); + } } else { - final boolean wasZero = row.ss.level == 0; - mController.setStreamVolume(stream, - wasZero ? row.lastAudibleLevel : 0); + mController.setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + if (row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } } } else { - mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); - if (row.ss.level == 0) { - mController.setStreamVolume(stream, 1); - } + final boolean vmute = row.ss.level == row.ss.levelMin; + mController.setStreamVolume(stream, + vmute ? row.lastAudibleLevel : row.ss.levelMin); } - } else { - final boolean vmute = row.ss.level == row.ss.levelMin; - mController.setStreamVolume(stream, - vmute ? row.lastAudibleLevel : row.ss.levelMin); - } - row.userAttempt = 0; // reset the grace period, slider updates immediately - }); + row.userAttempt = 0; // reset the grace period, slider updates immediately + }); + } else { + row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + } + } + + private void setRingerMode(int newRingerMode) { + Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); + incrementManualToggleCount(); + updateRingerH(); + provideTouchFeedbackH(newRingerMode); + mController.setRingerMode(newRingerMode, false); + maybeShowToastH(newRingerMode); + } + + private void setupRingerDrawer() { + mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container); + + if (mRingerDrawerContainer == null) { + return; + } + + if (!mShowVibrate) { + mRingerDrawerVibrate.setVisibility(GONE); + } + + // In portrait, add padding to the bottom to account for the height of the open ringer + // drawer. + if (!isLandscape()) { + mDialogView.setPadding( + mDialogView.getPaddingLeft(), + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom() + (mRingerCount - 1) * mRingerDrawerItemSize); + } else { + mDialogView.setPadding( + mDialogView.getPaddingLeft() + (mRingerCount - 1) * mRingerDrawerItemSize, + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom()); + } + + ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options)) + .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); + + mSelectedRingerContainer.setOnClickListener(view -> { + if (mIsRingerDrawerOpen) { + hideRingerDrawer(); + } else { + showRingerDrawer(); + } + }); + + mRingerDrawerVibrate.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE)); + mRingerDrawerMute.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_SILENT)); + mRingerDrawerNormal.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_NORMAL)); + + final int unselectedColor = Utils.getColorAccentDefaultColor(mContext); + final int selectedColor = Utils.getColorAttrDefaultColor( + mContext, android.R.attr.colorBackgroundFloating); + + // Add an update listener that animates the deselected icon to the unselected color, and the + // selected icon to the selected color. + mRingerDrawerIconColorAnimator.addUpdateListener( + anim -> { + final float currentValue = (float) anim.getAnimatedValue(); + final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, selectedColor, unselectedColor); + final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, unselectedColor, selectedColor); + + mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor); + mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor); + }); + mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRingerDrawerIconAnimatingDeselected.clearColorFilter(); + mRingerDrawerIconAnimatingSelected.clearColorFilter(); + } + }); + mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT); + } + + private ImageView getDrawerIconViewForMode(int mode) { + if (mode == RINGER_MODE_VIBRATE) { + return mRingerDrawerVibrateIcon; + } else if (mode == RINGER_MODE_SILENT) { + return mRingerDrawerMuteIcon; + } else { + return mRingerDrawerNormalIcon; + } + } + + /** + * Translation to apply form the origin (either top or left) to overlap the selection background + * with the given mode in the drawer. + */ + private float getTranslationInDrawerForRingerMode(int mode) { + return mode == RINGER_MODE_VIBRATE + ? -mRingerDrawerItemSize * 2 + : mode == RINGER_MODE_SILENT + ? -mRingerDrawerItemSize + : 0; + } + + /** Animates in the ringer drawer. */ + private void showRingerDrawer() { + // Show all ringer icons except the currently selected one, since we're going to animate the + // ringer button to that position. + mRingerDrawerVibrateIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE); + mRingerDrawerMuteIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE); + mRingerDrawerNormalIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE); + + // Hide the selection background - we use this to show a selection when one is + // tapped, so it should be invisible until that happens. However, position it below + // the currently selected ringer so that it's ready to animate. + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.setTranslationY( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } else { + mRingerDrawerNewSelectionBg.setTranslationX( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } + + // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer + // icon. + if (!isLandscape()) { + mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1)); + } else { + mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1)); + } + mRingerDrawerContainer.setAlpha(0f); + mRingerDrawerContainer.setVisibility(VISIBLE); + + // Animate the drawer up and visible. + mRingerDrawerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + // Vibrate is way farther up, so give the selected ringer icon a head start if + // vibrate is selected. + .setDuration(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION_SHORT + : DRAWER_ANIMATION_DURATION) + .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT + : 0) + .alpha(1f) + .translationX(0f) + .translationY(0f) + .start(); + + // Animate the selected ringer view up to that ringer's position in the drawer. + mSelectedRingerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setDuration(DRAWER_ANIMATION_DURATION) + .withEndAction(() -> + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE)); + + if (!isLandscape()) { + mSelectedRingerContainer.animate() + .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); } else { - row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mSelectedRingerContainer.animate() + .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); } + + mIsRingerDrawerOpen = true; + } + + /** Animates away the ringer drawer. */ + private void hideRingerDrawer() { + // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we + // don't want to be able to see it while it animates away. + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); + + mRingerDrawerContainer.animate() + .alpha(0f) + .setDuration(DRAWER_ANIMATION_DURATION) + .setStartDelay(0) + .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE)); + + if (!isLandscape()) { + mRingerDrawerContainer.animate() + .translationY(mRingerDrawerItemSize * 2) + .start(); + } else { + mRingerDrawerContainer.animate() + .translationX(mRingerDrawerItemSize * 2) + .start(); + } + + mSelectedRingerContainer.animate() + .translationX(0f) + .translationY(0f) + .start(); + + mIsRingerDrawerOpen = false; } public void initSettingsH() { @@ -555,12 +852,8 @@ public class VolumeDialogImpl implements VolumeDialog, mController.setStreamVolume(AudioManager.STREAM_RING, 1); } } - Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); - incrementManualToggleCount(); - updateRingerH(); - provideTouchFeedbackH(newRingerMode); - mController.setRingerMode(newRingerMode, false); - maybeShowToastH(newRingerMode); + + setRingerMode(newRingerMode); }); } updateRingerH(); @@ -809,6 +1102,8 @@ public class VolumeDialogImpl implements VolumeDialog, mDialog.dismiss(); tryToRemoveCaptionsTooltip(); mIsAnimatingDismiss = false; + + hideRingerDrawer(); }, 50)); if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f); animator.start(); @@ -889,12 +1184,14 @@ public class VolumeDialogImpl implements VolumeDialog, switch (mState.ringerModeInternal) { case AudioManager.RINGER_MODE_VIBRATE: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE, mContext.getString(R.string.volume_ringer_hint_mute)); mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); break; case AudioManager.RINGER_MODE_SILENT: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); mRingerIcon.setTag(Events.ICON_STATE_MUTE); addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT, mContext.getString(R.string.volume_ringer_hint_unmute)); @@ -904,11 +1201,13 @@ public class VolumeDialogImpl implements VolumeDialog, boolean muted = (mAutomute && ss.level == 0) || ss.muted; if (!isZenMuted && muted) { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_unmute)); mRingerIcon.setTag(Events.ICON_STATE_MUTE); } else { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer); if (mController.hasVibrator()) { addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_vibrate)); @@ -1075,8 +1374,6 @@ public class VolumeDialogImpl implements VolumeDialog, // update icon final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; - row.icon.setEnabled(iconEnabled); - row.icon.setAlpha(iconEnabled ? 1 : 0.5f); final int iconRes; if (isRingVibrate) { iconRes = R.drawable.ic_volume_ringer_vibrate; @@ -1092,7 +1389,7 @@ public class VolumeDialogImpl implements VolumeDialog, ? R.drawable.ic_volume_media_low : row.iconRes; } - row.icon.setImageResource(iconRes); + row.setIcon(iconRes); row.iconState = iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) @@ -1101,18 +1398,35 @@ public class VolumeDialogImpl implements VolumeDialog, || iconRes == R.drawable.ic_volume_media_low) ? Events.ICON_STATE_UNMUTE : Events.ICON_STATE_UNKNOWN; - if (iconEnabled) { - if (isRingStream) { - if (isRingVibrate) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); + + if (row.icon != null) { + if (iconEnabled) { + if (isRingStream) { + if (isRingVibrate) { + row.icon.setContentDescription(mContext.getString( + R.string.volume_stream_content_description_unmute, + getStreamLabelH(ss))); + } else { + if (mController.hasVibrator()) { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_vibrate_a11y + : R.string.volume_stream_content_description_vibrate, + getStreamLabelH(ss))); + } else { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_mute_a11y + : R.string.volume_stream_content_description_mute, + getStreamLabelH(ss))); + } + } + } else if (isA11yStream) { + row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (mController.hasVibrator()) { + if (ss.muted || mAutomute && ss.level == 0) { row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_vibrate_a11y - : R.string.volume_stream_content_description_vibrate, + R.string.volume_stream_content_description_unmute, getStreamLabelH(ss))); } else { row.icon.setContentDescription(mContext.getString( @@ -1122,23 +1436,9 @@ public class VolumeDialogImpl implements VolumeDialog, getStreamLabelH(ss))); } } - } else if (isA11yStream) { - row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (ss.muted || mAutomute && ss.level == 0) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); - } else { - row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_mute_a11y - : R.string.volume_stream_content_description_mute, - getStreamLabelH(ss))); - } + row.icon.setContentDescription(getStreamLabelH(ss)); } - } else { - row.icon.setContentDescription(getStreamLabelH(ss)); } // ensure tracking is disabled if zenMuted @@ -1167,22 +1467,29 @@ public class VolumeDialogImpl implements VolumeDialog, if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) { return; } - final ColorStateList tint = useActiveColoring + final ColorStateList colorTint = useActiveColoring ? Utils.getColorAccent(mContext) : Utils.getColorAttr(mContext, android.R.attr.colorForeground); final int alpha = useActiveColoring - ? Color.alpha(tint.getDefaultColor()) + ? Color.alpha(colorTint.getDefaultColor()) : getAlphaAttr(android.R.attr.secondaryContentAlpha); - if (tint == row.cachedTint) return; - row.slider.setProgressTintList(tint); - row.slider.setThumbTintList(tint); - row.slider.setProgressBackgroundTintList(tint); - row.slider.setAlpha(((float) alpha) / 255); - row.icon.setImageTintList(tint); - row.icon.setImageAlpha(alpha); - row.cachedTint = tint; + + final ColorStateList bgTint = Utils.getColorAttr( + mContext, android.R.attr.colorBackgroundFloating); + + row.sliderProgressSolid.setTintList(colorTint); + row.sliderBgIcon.setTintList(colorTint); + + row.sliderBgSolid.setTintList(bgTint); + row.sliderProgressIcon.setTintList(bgTint); + + if (row.icon != null) { + row.icon.setImageTintList(colorTint); + row.icon.setImageAlpha(alpha); + } + if (row.number != null) { - row.number.setTextColor(tint); + row.number.setTextColor(colorTint); row.number.setAlpha(alpha); } } @@ -1538,6 +1845,10 @@ public class VolumeDialogImpl implements VolumeDialog, private View view; private TextView header; private ImageButton icon; + private Drawable sliderBgSolid; + private AlphaTintDrawableWrapper sliderBgIcon; + private Drawable sliderProgressSolid; + private AlphaTintDrawableWrapper sliderProgressIcon; private SeekBar slider; private TextView number; private int stream; @@ -1555,5 +1866,69 @@ public class VolumeDialogImpl implements VolumeDialog, private int animTargetProgress; private int lastAudibleLevel = 1; private FrameLayout dndIcon; + + void setIcon(int iconRes) { + if (icon != null) { + icon.setImageResource(iconRes); + } + + sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes)); + sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes)); + } + } + + /** + * Click listener added to each ringer option in the drawer. This will initiate the animation to + * select and then close the ringer drawer, and actually change the ringer mode. + */ + private class RingerDrawerItemClickListener implements View.OnClickListener { + private final int mClickedRingerMode; + + RingerDrawerItemClickListener(int clickedRingerMode) { + mClickedRingerMode = clickedRingerMode; + } + + @Override + public void onClick(View view) { + setRingerMode(mClickedRingerMode); + + mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode); + mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode( + mState.ringerModeInternal); + + // Begin switching the selected icon and deselected icon colors since the background is + // going to animate behind the new selection. + mRingerDrawerIconColorAnimator.start(); + + mSelectedRingerContainer.setVisibility(View.INVISIBLE); + mRingerDrawerNewSelectionBg.setAlpha(1f); + mRingerDrawerNewSelectionBg.animate() + .setInterpolator(Interpolators.ACCELERATE_DECELERATE) + .setDuration(DRAWER_ANIMATION_DURATION_SHORT) + .withEndAction(() -> { + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mSelectedRingerContainer.setTranslationY( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } else { + mSelectedRingerContainer.setTranslationX( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } + + mSelectedRingerContainer.setVisibility(VISIBLE); + hideRingerDrawer(); + }); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.animate() + .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } else { + mRingerDrawerNewSelectionBg.animate() + .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 6e7aed064159..afeda967c8c1 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -45,6 +45,7 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.ZenModeConfig; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.view.View; import androidx.annotation.NonNull; @@ -86,10 +87,12 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; import java.util.function.Supplier; @@ -248,38 +251,19 @@ public class BubblesManager implements Dumpable { }); mSysuiProxy = new Bubbles.SysuiProxy() { - private <T> T executeBlockingForResult(Supplier<T> runnable, Executor executor, - Class clazz) { - if (Looper.myLooper() == Looper.getMainLooper()) { - return runnable.get(); - } - final T[] result = (T[]) Array.newInstance(clazz, 1); - final CountDownLatch latch = new CountDownLatch(1); - executor.execute(() -> { - result[0] = runnable.get(); - latch.countDown(); - }); - try { - latch.await(); - return result[0]; - } catch (InterruptedException e) { - return null; - } - } - @Override - @Nullable - public BubbleEntry getPendingOrActiveEntry(String key) { - return executeBlockingForResult(() -> { + public void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback) { + sysuiMainExecutor.execute(() -> { NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key); - return entry == null ? null : notifToBubbleEntry(entry); - }, sysuiMainExecutor, BubbleEntry.class); + callback.accept(entry == null ? null : notifToBubbleEntry(entry)); + }); } @Override - public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) { - return executeBlockingForResult(() -> { + public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys, + Consumer<List<BubbleEntry>> callback) { + sysuiMainExecutor.execute(() -> { List<BubbleEntry> result = new ArrayList<>(); List<NotificationEntry> activeEntries = mNotificationEntryManager.getActiveNotificationsForCurrentUser(); @@ -291,27 +275,8 @@ public class BubblesManager implements Dumpable { result.add(notifToBubbleEntry(entry)); } } - return result; - }, sysuiMainExecutor, List.class); - } - - @Override - public boolean isNotificationShadeExpand() { - return executeBlockingForResult(() -> { - return mNotificationShadeWindowController.getPanelExpanded(); - }, sysuiMainExecutor, Boolean.class); - } - - @Override - public boolean shouldBubbleUp(String key) { - return executeBlockingForResult(() -> { - final NotificationEntry entry = - mNotificationEntryManager.getPendingOrActiveNotif(key); - if (entry != null) { - return mNotificationInterruptStateProvider.shouldBubbleUp(entry); - } - return false; - }, sysuiMainExecutor, Boolean.class); + callback.accept(result); + }); } @Override @@ -587,7 +552,20 @@ public class BubblesManager implements Dumpable { } void onRankingUpdate(RankingMap rankingMap) { - mBubbles.onRankingUpdated(rankingMap); + String[] orderedKeys = rankingMap.getOrderedKeys(); + HashMap<String, Pair<BubbleEntry, Boolean>> pendingOrActiveNotif = new HashMap<>(); + for (int i = 0; i < orderedKeys.length; i++) { + String key = orderedKeys[i]; + NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key); + BubbleEntry bubbleEntry = entry != null + ? notifToBubbleEntry(entry) + : null; + boolean shouldBubbleUp = entry != null + ? mNotificationInterruptStateProvider.shouldBubbleUp(entry) + : false; + pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp)); + } + mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif); } /** diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 449db61a0fbb..fba0b0079012 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -20,7 +20,6 @@ import static android.os.Process.THREAD_PRIORITY_DISPLAY; import android.animation.AnimationHandler; import android.app.ActivityTaskManager; -import android.app.IActivityManager; import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; @@ -73,7 +72,6 @@ import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PipAppOpsListener; -import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; @@ -211,8 +209,8 @@ public abstract class WMShellBaseModule { @Provides static SizeCompatUIController provideSizeCompatUIController(Context context, DisplayController displayController, DisplayImeController imeController, - @ShellMainThread ShellExecutor mainExecutor) { - return new SizeCompatUIController(context, displayController, imeController, mainExecutor); + SyncTransactionQueue syncQueue) { + return new SizeCompatUIController(context, displayController, imeController, syncQueue); } @WMSingleton diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt new file mode 100644 index 000000000000..9278570714fe --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.globalactions.GlobalActionsComponent +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.wm.shell.TaskViewFactory +import dagger.Lazy +import java.util.Optional +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ControlActionCoordinatorImplTest : SysuiTestCase() { + + @Mock + private lateinit var uiController: ControlsUiController + @Mock + private lateinit var lazyUiController: Lazy<ControlsUiController> + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var bgExecutor: DelayableExecutor + @Mock + private lateinit var uiExecutor: DelayableExecutor + @Mock + private lateinit var activityStarter: ActivityStarter + @Mock + private lateinit var globalActionsComponent: GlobalActionsComponent + @Mock + private lateinit var taskViewFactory: Optional<TaskViewFactory> + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private lateinit var cvh: ControlViewHolder + + companion object { + fun <T> any(): T = Mockito.any<T>() + + private val ID = "id" + } + + private lateinit var coordinator: ControlActionCoordinatorImpl + private lateinit var action: ControlActionCoordinatorImpl.Action + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + coordinator = spy(ControlActionCoordinatorImpl( + mContext, + bgExecutor, + uiExecutor, + activityStarter, + keyguardStateController, + globalActionsComponent, + taskViewFactory, + getFakeBroadcastDispatcher(), + lazyUiController + )) + + `when`(cvh.cws.ci.controlId).thenReturn(ID) + action = spy(coordinator.Action(ID, {}, false)) + doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean()) + } + + @Test + fun testToggleRunsWhenUnlocked() { + `when`(keyguardStateController.isShowing()).thenReturn(false) + + coordinator.toggle(cvh, "", true) + verify(coordinator).bouncerOrRun(action) + verify(action).invoke() + } + + @Test + fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() { + `when`(keyguardStateController.isShowing()).thenReturn(true) + `when`(keyguardStateController.isUnlocked()).thenReturn(false) + + coordinator.toggle(cvh, "", true) + verify(coordinator).bouncerOrRun(action) + verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean()) + verify(action, never()).invoke() + + // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction + reset(action) + coordinator.runPendingAction(ID) + verify(action, never()).invoke() + + `when`(keyguardStateController.isUnlocked()).thenReturn(true) + reset(action) + coordinator.runPendingAction(ID) + verify(action).invoke() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java index 069699c271f7..6d8c372a061b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java @@ -173,4 +173,30 @@ public class DozeUiTest extends SysuiTestCase { mDozeUi.transitionTo(UNINITIALIZED, DOZE); verify(mHost).setAnimateWakeup(eq(false)); } + + @Test + public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() { + when(mDozeParameters.getAlwaysOn()).thenReturn(true); + when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + + // Tell doze that keyguard is not visible. + mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */); + + // Since we're controlling the unlocked screen off animation, verify that we've asked to + // control the screen off animation despite being unlocked. + verify(mDozeParameters).setControlScreenOffAnimation(true); + } + + @Test + public void controlScreenOffFalseWhenKeyguardNotShowingAndControlUnlockedScreenOffFalse() { + when(mDozeParameters.getAlwaysOn()).thenReturn(true); + when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false); + + // Tell doze that keyguard is not visible. + mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */); + + // Since we're not controlling the unlocked screen off animation, verify that we haven't + // asked to control the screen off animation since we're unlocked. + verify(mDozeParameters).setControlScreenOffAnimation(false); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java index a52a598ee7ec..0457100e0294 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java @@ -19,7 +19,7 @@ package com.android.systemui.emergency; import android.app.Activity; import android.os.Bundle; -import com.android.systemui.R; +import com.android.systemui.tests.R; /** * Test activity for resolving {@link EmergencyGesture#ACTION_LAUNCH_EMERGENCY} action. diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 00943bc53bfd..b8c37fde2ce3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -18,6 +18,8 @@ package com.android.systemui.keyguard; import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -27,13 +29,17 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.view.View; +import com.android.systemui.R; import androidx.test.filters.SmallTest; import com.android.internal.widget.LockPatternUtils; @@ -45,6 +51,7 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.util.DeviceConfigProxy; @@ -117,4 +124,20 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.mViewMediatorCallback.keyguardGone(); verify(mStatusBarKeyguardViewManager).setKeyguardGoingAwayState(eq(false)); } + + @Test + public void testIsAnimatingScreenOff() { + when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + + mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false); + mViewMediator.setDozing(true); + + // Mid-doze, we should be animating the screen off animation. + mViewMediator.onDozeAmountChanged(0.5f, 0.5f); + assertTrue(mViewMediator.isAnimatingScreenOff()); + + // Once we're 100% dozed, the screen off animation should be completed. + mViewMediator.onDozeAmountChanged(1f, 1f); + assertFalse(mViewMediator.isAnimatingScreenOff()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 2db224f85eb0..e88c72860ed8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -315,10 +315,9 @@ class MediaDataManagerTest : SysuiTestCase() { } mediaDataManager.onNotificationAdded(KEY, notif) - // THEN it loads and uses the default background color + // THEN it still loads assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor)) - assertThat(mediaDataCaptor.value!!.backgroundColor).isEqualTo(DEFAULT_COLOR) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java index b452d3a79814..1ec1da44c0b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java @@ -98,7 +98,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mQSCarrierGroupController = new QSCarrierGroupController.Builder( mActivityStarter, handler, TestableLooper.get(this).getLooper(), - mNetworkController, mCarrierTextControllerBuilder) + mNetworkController, mCarrierTextControllerBuilder, mContext) .setQSCarrierGroup(mQSCarrierGroup) .build(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java index c1c637129d85..580f800fbc44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java @@ -57,6 +57,8 @@ import org.mockito.stubbing.Answer; @SmallTest @RunWith(AndroidTestingRunner.class) public class ScrollCaptureClientTest extends SysuiTestCase { + private static final float MAX_PAGES = 3f; + private Context mContext; private IWindowManager mWm; @@ -96,7 +98,7 @@ public class ScrollCaptureClientTest extends SysuiTestCase { Connection conn = mConnectionConsumer.getValue(); - conn.start(mSessionConsumer); + conn.start(mSessionConsumer, MAX_PAGES); verify(mSessionConsumer, timeout(100)).accept(any(Session.class)); Session session = mSessionConsumer.getValue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java index bd3725942eca..4c84df2769a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java @@ -34,7 +34,7 @@ public class ScrollViewActivity extends Activity { linearLayout.setOrientation(LinearLayout.VERTICAL); TextView text = new TextView(this); text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40); - text.setText(com.android.systemui.R.string.test_content); + text.setText(com.android.systemui.tests.R.string.test_content); linearLayout.addView(text); scrollView.addView(linearLayout); setContentView(scrollView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java index d131dceb70db..e16d4d71efe7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java @@ -36,16 +36,20 @@ import static org.junit.Assert.assertTrue; import android.app.Notification; import android.app.NotificationChannel; +import android.os.Handler; import android.os.UserHandle; -import android.provider.Settings; +import android.provider.DeviceConfig; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.util.Pair; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.util.DeviceConfigProxyFake; import junit.framework.Assert; @@ -55,38 +59,44 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper public class AssistantFeedbackControllerTest extends SysuiTestCase { - private static final int ON = 1; - private static final int OFF = 0; private static final String TEST_PACKAGE_NAME = "test_package"; private static final int TEST_UID = 1; private AssistantFeedbackController mAssistantFeedbackController; + private DeviceConfigProxyFake mProxyFake; + private TestableLooper mTestableLooper; + private StatusBarNotification mSbn; @Before public void setUp() { - mAssistantFeedbackController = new AssistantFeedbackController(mContext); - switchSetting(ON); + mProxyFake = new DeviceConfigProxyFake(); + mTestableLooper = TestableLooper.get(this); + mAssistantFeedbackController = new AssistantFeedbackController( + new Handler(mTestableLooper.getLooper()), + mContext, mProxyFake); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); } @Test - public void testUserControls_settingDisabled() { - switchSetting(OFF); + public void testFlagDisabled() { + switchFlag("false"); assertFalse(mAssistantFeedbackController.isFeedbackEnabled()); } @Test - public void testUserControls_settingEnabled() { + public void testFlagEnabled() { + switchFlag("true"); assertTrue(mAssistantFeedbackController.isFeedbackEnabled()); } @Test - public void testFeedback_settingDisabled() { - switchSetting(OFF); + public void testFeedback_flagDisabled() { + switchFlag("false"); assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus( getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED))); assertFalse(mAssistantFeedbackController.showFeedbackIndicator( @@ -95,6 +105,7 @@ public class AssistantFeedbackControllerTest extends SysuiTestCase { @Test public void testFeedback_changedImportance() { + switchFlag("true"); NotificationEntry entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_UNCHANGED); assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry)); assertTrue(mAssistantFeedbackController.showFeedbackIndicator(entry)); @@ -110,6 +121,7 @@ public class AssistantFeedbackControllerTest extends SysuiTestCase { @Test public void testFeedback_changedRanking() { + switchFlag("true"); NotificationEntry entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_PROMOTED); assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry)); @@ -121,8 +133,8 @@ public class AssistantFeedbackControllerTest extends SysuiTestCase { } @Test - public void testGetFeedbackResources_settingDisabled() { - switchSetting(OFF); + public void testGetFeedbackResources_flagDisabled() { + switchFlag("false"); Assert.assertEquals(new Pair(0, 0), mAssistantFeedbackController.getFeedbackResources( getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED))); } @@ -138,9 +150,10 @@ public class AssistantFeedbackControllerTest extends SysuiTestCase { .build(); } - private void switchSetting(int setting) { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, setting); - mAssistantFeedbackController.update(null); + private void switchFlag(String enabled) { + mProxyFake.setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK, + enabled, false); + mTestableLooper.processAllMessages(); } } 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 7eeae67c9fdf..e6287e7063d3 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 @@ -38,8 +38,8 @@ import android.widget.RemoteViews; import androidx.palette.graphics.Palette; import androidx.test.runner.AndroidJUnit4; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.tests.R; import org.junit.After; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 6b0a23f2b4ef..2e2945e28161 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -50,7 +50,6 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -61,6 +60,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.policy.InflatedSmartReplies; import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater; +import com.android.systemui.tests.R; import org.junit.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 6fcc7fa9376c..64a7bee3c8dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -43,7 +43,6 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; -import com.android.systemui.R; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.media.MediaFeatureFlag; @@ -68,6 +67,7 @@ import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.InflatedSmartReplies; +import com.android.systemui.tests.R; import com.android.systemui.wmshell.BubblesManager; import com.android.systemui.wmshell.BubblesTestActivity; import com.android.wm.shell.bubbles.Bubbles; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index a147c8d0f121..45f7c5a6fdc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -24,10 +24,10 @@ import android.widget.RemoteViews; import androidx.test.filters.SmallTest; -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 com.android.systemui.tests.R; import org.junit.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index b9fd75ef5fda..421c6f4aab0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; @@ -114,4 +116,37 @@ public class DozeParametersTest extends SysuiTestCase { assertThat(mDozeParameters.getAlwaysOn()).isFalse(); } + + @Test + public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() { + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); + mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); + when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(true); + + assertTrue(mDozeParameters.shouldControlUnlockedScreenOff()); + + // Trigger the setter for the current value. + mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff()); + + // We should have asked power manager not to doze after screen off no matter what, since + // we're animating and controlling screen off. + verify(mPowerManager).setDozeAfterScreenOff(eq(false)); + } + + @Test + public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() { + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); + mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); + when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(false); + + assertFalse(mDozeParameters.shouldControlUnlockedScreenOff()); + + // Trigger the setter for the current value. + mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff()); + + // We should have asked power manager to doze only if we're not controlling screen off + // normally. + verify(mPowerManager).setDozeAfterScreenOff( + eq(!mDozeParameters.shouldControlScreenOff())); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index ee1d758e7ae2..9b623f950505 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -393,10 +393,11 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private void positionClock() { mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight, - mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY, + mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, + 0 /* userSwitchHeight */, mPreferredClockY, 0 /* userSwitchPreferredY */, mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */, - 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, - mQsExpansion, mCutoutTopInset); + 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion, + mCutoutTopInset); mClockPositionAlgorithm.run(mClockPosition); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index c07ba723ab43..e788a1c0954b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -37,6 +37,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; import android.os.PowerManager; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.DisplayMetrics; @@ -47,6 +48,8 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -57,7 +60,9 @@ import com.android.keyguard.KeyguardClockSwitchController; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; +import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; @@ -193,6 +198,12 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; @Mock + private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; + @Mock + private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; + @Mock + private QSDetailDisplayer mQSDetailDisplayer; + @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; @@ -213,11 +224,13 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private NotificationsQuickSettingsContainer mNotificationContainerParent; - @Mock private AmbientState mAmbientState; + @Mock + private UserManager mUserManager; + private NotificationPanelViewController mNotificationPanelViewController; private View.AccessibilityDelegate mAccessibiltyDelegate; + private NotificationsQuickSettingsContainer mNotificationContainerParent; @Before public void setup() { @@ -250,6 +263,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); + mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null); when(mView.findViewById(R.id.notification_container_parent)) .thenReturn(mNotificationContainerParent); FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder( @@ -299,11 +313,14 @@ public class NotificationPanelViewTest extends SysuiTestCase { mBiometricUnlockController, mStatusBarKeyguardViewManager, mNotificationStackScrollLayoutController, mKeyguardStatusViewComponentFactory, + mKeyguardQsUserSwitchComponentFactory, + mKeyguardUserSwitcherComponentFactory, + mQSDetailDisplayer, mGroupManager, mNotificationAreaController, mAuthController, - new QSDetailDisplayer(), mScrimController, + mUserManager, mMediaDataManager, mAmbientState, mFeatureFlags, @@ -425,16 +442,11 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Test public void testAllChildrenOfNotificationContainer_haveIds() { - when(mNotificationContainerParent.getChildCount()).thenReturn(2); when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true); when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true); - View view1 = new View(mContext); - view1.setId(1); - when(mNotificationContainerParent.getChildAt(0)).thenReturn(view1); - - View view2 = mock(View.class); - when(mNotificationContainerParent.getChildAt(1)).thenReturn(view2); + mNotificationContainerParent.addView(newViewWithId(1)); + mNotificationContainerParent.addView(newViewWithId(View.NO_ID)); mNotificationPanelViewController.updateResources(); @@ -442,6 +454,51 @@ public class NotificationPanelViewTest extends SysuiTestCase { assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID); } + @Test + public void testSinglePaneShadeLayout_isAlignedToParent() { + when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false); + mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame)); + mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller)); + + mNotificationPanelViewController.updateResources(); + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mNotificationContainerParent); + ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout; + ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint( + R.id.notification_stack_scroller).layout; + assertThat(qsFrameLayout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID); + assertThat(stackScrollerLayout.startToStart).isEqualTo(ConstraintSet.PARENT_ID); + } + + @Test + public void testSplitShadeLayout_isAlignedToGuideline() { + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true); + when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true); + mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame)); + mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller)); + + mNotificationPanelViewController.updateResources(); + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mNotificationContainerParent); + ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout; + ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint( + R.id.notification_stack_scroller).layout; + assertThat(qsFrameLayout.endToEnd).isEqualTo(R.id.qs_edge_guideline); + assertThat(stackScrollerLayout.startToStart).isEqualTo(R.id.qs_edge_guideline); + } + + private View newViewWithId(int id) { + View view = new View(mContext); + view.setId(id); + ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + // required as cloning ConstraintSet fails if view doesn't have layout params + view.setLayoutParams(layoutParams); + return view; + } + private void onTouchEvent(MotionEvent ev) { mTouchHandler.onTouch(mView, ev); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java index c212cf3ee769..67c1a086bb33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java @@ -26,12 +26,12 @@ import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.settingslib.R; import com.android.settingslib.mobile.TelephonyIcons; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.tests.R; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt index fc1a79105db1..e479882ac50a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt @@ -60,9 +60,9 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { @Mock private lateinit var layoutInflater: LayoutInflater @Mock - private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher + private lateinit var keyguardUserSwitcherController: KeyguardUserSwitcherController - private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter + private lateinit var adapter: KeyguardUserSwitcherController.KeyguardUserAdapter private lateinit var picture: Bitmap @Before @@ -72,8 +72,11 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater) `when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean())) .thenReturn(inflatedUserDetailItemView) - adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController, - keyguardUserSwitcher) + adapter = KeyguardUserSwitcherController.KeyguardUserAdapter( + mContext, + mContext.resources, + LayoutInflater.from(mContext), + userSwitcherController, keyguardUserSwitcherController) picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user)) } @@ -118,11 +121,11 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { } @Test - fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() { + fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsSameType() { val v: UserDetailItemView? = createViewFromSameType( isCurrentUser = true, isGuestUser = false) assertNotNull(v) - verify(v)!!.setOnClickListener(null) + verify(v)!!.setOnClickListener(adapter) } @Test @@ -150,11 +153,11 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { } @Test - fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() { + fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() { val v: UserDetailItemView? = createViewFromDifferentType( isCurrentUser = true, isGuestUser = false) assertNotNull(v) - verify(v)!!.setOnClickListener(null) + verify(v)!!.setOnClickListener(adapter) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index f8b63835551f..89cc2b574398 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -430,6 +430,10 @@ public class NetworkControllerBaseTest extends SysuiTestCase { updateSignalStrength(); } + public void setImsType(int imsType) { + mMobileSignalController.setImsType(imsType); + } + public void setIsGsm(boolean gsm) { when(mSignalStrength.isGsm()).thenReturn(gsm); updateSignalStrength(); @@ -632,6 +636,14 @@ public class NetworkControllerBaseTest extends SysuiTestCase { } } + protected void verifyLastCallStrength(int icon) { + ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class); + verify(mCallbackHandler, Mockito.atLeastOnce()).setCallIndicator( + iconArg.capture(), + anyInt()); + assertEquals("Call strength, in status bar", icon, (int) iconArg.getValue().icon); + } + protected void assertNetworkNameEquals(String expected) { assertEquals("Network name", expected, mMobileSignalController.getState().networkName); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index 10166cb0c43f..fc1a08ac3874 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -15,6 +15,7 @@ import android.net.NetworkInfo; import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.telephony.CellSignalStrength; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -243,6 +244,28 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } } + @Test + public void testCallStrengh() { + String testSsid = "Test SSID"; + setWifiEnabled(true); + setWifiState(true, testSsid); + // Set the ImsType to be IMS_TYPE_WLAN + setImsType(2); + setWifiLevel(1); + for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) { + setWifiLevel(testLevel); + verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[testLevel]); + } + // Set the ImsType to be IMS_TYPE_WWAN + setImsType(1); + for (int testStrength = 0; + testStrength < CellSignalStrength.getNumSignalStrengthLevels(); testStrength++) { + setupDefaultSignal(); + setLevel(testStrength); + verifyLastCallStrength(TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[testStrength]); + } + } + protected void setWifiActivity(int activity) { // TODO: Not this, because this variable probably isn't sticking around. mNetworkController.mWifiSignalController.setActivity(activity); diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index 6c99efcc7128..45828c3f73ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -34,10 +34,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.om.OverlayManager; @@ -73,11 +75,12 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { private static final String TEST_DISABLED_PREFIX = "com.example."; private static final String TEST_ENABLED_PREFIX = "com.example.enabled."; - private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap(); + private static final Map<String, OverlayIdentifier> ALL_CATEGORIES_MAP = Maps.newArrayMap(); static { for (String category : THEME_CATEGORIES) { - ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category); + ALL_CATEGORIES_MAP.put(category, + new OverlayIdentifier(TEST_DISABLED_PREFIX + category)); } } @@ -157,27 +160,26 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_allEnabledExclusively() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); verify(mOverlayManager).commit(any()); - for (String overlayPackage : ALL_CATEGORIES_MAP.values()) { - verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)), - eq(true), eq(TEST_USER.getIdentifier())); + for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { + verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), + eq(TEST_USER.getIdentifier())); } } @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); - for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) { + for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { - verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(entry.getValue())), - eq(true), eq(UserHandle.SYSTEM.getIdentifier())); + verify(mTransactionBuilder).setEnabled(eq(entry.getValue()), eq(true), + eq(UserHandle.SYSTEM.getIdentifier())); } else { verify(mTransactionBuilder, never()).setEnabled( - eq(new OverlayIdentifier(entry.getValue())), - eq(true), eq(UserHandle.SYSTEM.getIdentifier())); + eq(entry.getValue()), eq(true), eq(UserHandle.SYSTEM.getIdentifier())); } } } @@ -187,19 +189,34 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); UserHandle newUserHandle = UserHandle.of(10); userHandles.add(newUserHandle); - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles); + + for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { + verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), + eq(TEST_USER.getIdentifier())); + verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), + eq(newUserHandle.getIdentifier())); + } + } + + @Test + public void applyCurrentUserOverlays_createsPendingOverlays() { + Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); + UserHandle newUserHandle = UserHandle.of(10); + userHandles.add(newUserHandle); + FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] { + mock(FabricatedOverlay.class) + }; + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles); - for (String overlayPackage : ALL_CATEGORIES_MAP.values()) { - verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)), - eq(true), eq(TEST_USER.getIdentifier())); - verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)), - eq(true), eq(newUserHandle.getIdentifier())); + for (FabricatedOverlay overlay : pendingCreation) { + verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay)); } } @Test public void allCategoriesSpecified_overlayManagerNotQueried() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); verify(mOverlayManager, never()) .getOverlayInfosForTarget(anyString(), any(UserHandle.class)); @@ -207,15 +224,15 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() { - Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); + Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); - mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); - for (String overlayPackage : categoryToPackage.values()) { - verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)), - eq(true), eq(TEST_USER.getIdentifier())); + for (OverlayIdentifier overlayPackage : categoryToPackage.values()) { + verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), + eq(TEST_USER.getIdentifier())); } verify(mTransactionBuilder).setEnabled( eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)), @@ -227,7 +244,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void zeroCategoriesSpecified_allDisabled() { - mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES); for (String category : THEME_CATEGORIES) { verify(mTransactionBuilder).setEnabled( @@ -238,10 +255,10 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void nonThemeCategorySpecified_ignored() { - Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); - categoryToPackage.put("blah.category", "com.example.blah.category"); + Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); + categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category")); - mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); verify(mTransactionBuilder, never()).setEnabled( eq(new OverlayIdentifier("com.example.blah.category")), eq(false), @@ -253,10 +270,10 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void overlayManagerOnlyQueriedForUnspecifiedPackages() { - Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); + Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); - mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM); verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE, @@ -270,7 +287,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName, String category, boolean enabled) { - return new OverlayInfo(packageName, targetPackageName, null, category, "", - enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false); + return new OverlayInfo(packageName, null, targetPackageName, null, category, "", + enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false, + false); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index d33fac0d3b25..f7f8d03da1c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.theme; -import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE; -import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER; @@ -27,12 +25,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.WallpaperColors; import android.app.WallpaperManager; +import android.content.om.FabricatedOverlay; +import android.content.om.OverlayIdentifier; import android.graphics.Color; import android.os.Handler; import android.os.UserHandle; @@ -40,11 +41,13 @@ import android.os.UserManager; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; @@ -56,7 +59,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.List; import java.util.Map; import java.util.concurrent.Executor; @@ -85,6 +87,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { private KeyguardStateController mKeyguardStateController; @Mock private DumpManager mDumpManager; + @Mock + private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; @Captor @@ -93,10 +97,20 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); + when(mFeatureFlags.isMonetEnabled()).thenReturn(true); mThemeOverlayController = new ThemeOverlayController(null /* context */, mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController, - mDumpManager); + mDumpManager, mFeatureFlags) { + @Nullable + @Override + protected FabricatedOverlay getOverlay(int color, int type) { + FabricatedOverlay overlay = mock(FabricatedOverlay.class); + when(overlay.getIdentifier()) + .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000))); + return overlay; + } + }; mThemeOverlayController.start(); if (USE_LOCK_SCREEN_WALLPAPER) { @@ -106,10 +120,6 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null), eq(UserHandle.USER_ALL)); verify(mDumpManager).registerDumpable(any(), any()); - - List<Integer> colorList = List.of(Color.RED, Color.BLUE, 0x0CCCCC, 0x000CCC); - when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList); - when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList); } @Test @@ -128,17 +138,17 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); - ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays = + ArgumentCaptor.forClass(Map.class); - verify(mThemeOverlayApplier).getAvailableSystemColors(); - verify(mThemeOverlayApplier).getAvailableAccentColors(); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any()); + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) - .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "FF0000"); + .isEqualTo(new OverlayIdentifier("ffff0000")); assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) - .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "0000FF"); + .isEqualTo(new OverlayIdentifier("ff0000ff")); // Should not ask again if changed to same value mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); @@ -146,69 +156,49 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test - public void onWallpaperColorsChanged_whiteTheme() { - WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.WHITE), - Color.valueOf(Color.BLUE), null); - mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); - ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class); - - verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any()); - - // Assert that we received the colors that we were expecting - assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse(); - } - - @Test - public void onWallpaperColorsChanged_blackTheme() { - WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.BLACK), + public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() { + // Should ask for a new theme when wallpaper colors change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); - mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); - ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any()); - - // Assert that we received the colors that we were expecting - assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse(); - } + String jsonString = + "{\"android.theme.customization.system_palette\":\"override.package.name\"}"; + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); - @Test - public void onWallpaperColorsChanged_addsLeadingZerosToColors() { - // Should ask for a new theme when wallpaper colors change - WallpaperColors mainColors = new WallpaperColors(Color.valueOf(0x0CCCCC), - Color.valueOf(0x000CCC), null); mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); - ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays = + ArgumentCaptor.forClass(Map.class); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any()); + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) - .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "0CCCCC"); - assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) - .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "000CCC"); + .isEqualTo(new OverlayIdentifier("override.package.name")); } @Test - public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() { - // Should ask for a new theme when wallpaper colors change + public void onWallpaperColorsChanged_parsesColorsFromWallpaperPicker() { WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); String jsonString = - "{\"android.theme.customization.system_palette\":\"override.package.name\"}"; + "{\"android.theme.customization.system_palette\":\"00FF00\"}"; when(mSecureSettings.getStringForUser( eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) .thenReturn(jsonString); mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); - ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class); + ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays = + ArgumentCaptor.forClass(Map.class); - verify(mThemeOverlayApplier).getAvailableSystemColors(); - verify(mThemeOverlayApplier).getAvailableAccentColors(); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any()); + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) - .isEqualTo("override.package.name"); + .isEqualTo(new OverlayIdentifier("ff00ff00")); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index c0af15b1f96d..203ece9532ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -19,8 +19,8 @@ import android.testing.LeakCheck; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; +import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; -import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import java.util.List; @@ -66,7 +66,7 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void setNoCallingIcons(String slot, List<NoCallingIconState> states) { + public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) { } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c0856892dc44..3cea17567173 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -22,6 +22,7 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.KeyguardManager; @@ -37,6 +38,8 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Prefs; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; @@ -58,6 +61,11 @@ import java.util.function.Predicate; public class VolumeDialogImplTest extends SysuiTestCase { VolumeDialogImpl mDialog; + View mActiveRinger; + View mDrawerContainer; + View mDrawerVibrate; + View mDrawerMute; + View mDrawerNormal; @Mock VolumeDialogController mController; @@ -80,6 +88,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); + + mActiveRinger = mDialog.getDialogView().findViewById( + R.id.volume_new_ringer_active_icon_container); + mDrawerContainer = mDialog.getDialogView().findViewById(R.id.volume_drawer_container); + mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate); + mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute); + mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal); + + Prefs.putInt(mContext, + Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, + VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1); } private State createShellState() { @@ -207,6 +226,48 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mController, never()).vibrate(any()); } + @Test + public void testSelectVibrateFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerVibrate.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_VIBRATE, false); + } + + @Test + public void testSelectMuteFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerMute.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_SILENT, false); + } + + @Test + public void testSelectNormalFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerNormal.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + } + /* @Test public void testContentDescriptions() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java index f4d96a123624..ab329c894fb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java @@ -20,7 +20,7 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import com.android.systemui.R; +import com.android.systemui.tests.R; /** * Referenced by NotificationTestHelper#makeBubbleMetadata diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java index b36626f9d736..0d323fb186c9 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java @@ -23,6 +23,9 @@ import android.os.Process; import android.os.ShellCommand; import android.os.UserHandle; +import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; + import java.io.PrintWriter; /** @@ -31,11 +34,13 @@ import java.io.PrintWriter; final class AccessibilityShellCommand extends ShellCommand { final @NonNull AccessibilityManagerService mService; final @NonNull SystemActionPerformer mSystemActionPerformer; + final @NonNull WindowManagerInternal mWindowManagerService; AccessibilityShellCommand(@NonNull AccessibilityManagerService service, @NonNull SystemActionPerformer systemActionPerformer) { mService = service; mSystemActionPerformer = systemActionPerformer; + mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); } @Override @@ -53,6 +58,10 @@ final class AccessibilityShellCommand extends ShellCommand { case "call-system-action": { return runCallSystemAction(); } + case "start-trace": + return startTrace(); + case "stop-trace": + return stopTrace(); } return -1; } @@ -98,6 +107,20 @@ final class AccessibilityShellCommand extends ShellCommand { return -1; } + private int startTrace() { + WindowManagerInternal.AccessibilityControllerInternal ac = + mWindowManagerService.getAccessibilityController(); + ac.startTrace(); + return 0; + } + + private int stopTrace() { + WindowManagerInternal.AccessibilityControllerInternal ac = + mWindowManagerService.getAccessibilityController(); + ac.stopTrace(); + return 0; + } + private Integer parseUserId() { final String option = getNextOption(); if (option != null) { @@ -123,5 +146,9 @@ final class AccessibilityShellCommand extends ShellCommand { pw.println(" Get whether binding to services provided by instant apps is allowed."); pw.println(" call-system-action <ACTION_ID>"); pw.println(" Calls the system action with the given action id."); + pw.println(" start-trace"); + pw.println(" Start the debug tracing."); + pw.println(" stop-trace"); + pw.println(" Stop the debug tracing."); } -}
\ No newline at end of file +} diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 38275f7cd348..6c30999f63a4 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1245,9 +1245,10 @@ public class BackupManagerService extends IBackupManager.Stub { @Override public IRestoreSession beginRestoreSessionForUser( - int userId, String packageName, String transportID) throws RemoteException { + int userId, String packageName, String transportID, + @OperationType int operationType) throws RemoteException { return isUserReadyForBackup(userId) - ? beginRestoreSession(userId, packageName, transportID) : null; + ? beginRestoreSession(userId, packageName, transportID, operationType) : null; } /** @@ -1256,13 +1257,15 @@ public class BackupManagerService extends IBackupManager.Stub { */ @Nullable public IRestoreSession beginRestoreSession( - @UserIdInt int userId, String packageName, String transportName) { + @UserIdInt int userId, String packageName, String transportName, + @OperationType int operationType) { UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()"); return userBackupManagerService == null ? null - : userBackupManagerService.beginRestoreSession(packageName, transportName); + : userBackupManagerService.beginRestoreSession(packageName, transportName, + operationType); } @Override @@ -1347,15 +1350,15 @@ public class BackupManagerService extends IBackupManager.Stub { if (!isUserReadyForBackup(userId)) { return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } - return requestBackup(userId, packages, observer, monitor, flags); + return requestBackup(userId, packages, observer, monitor, flags, OperationType.BACKUP); } @Override public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags) + IBackupManagerMonitor monitor, int flags, @OperationType int operationType) throws RemoteException { return requestBackup(binderGetCallingUserId(), packages, - observer, monitor, flags); + observer, monitor, flags, operationType); } /** @@ -1367,13 +1370,15 @@ public class BackupManagerService extends IBackupManager.Stub { String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, - int flags) { + int flags, + @OperationType int operationType) { UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(userId, "requestBackup()"); return userBackupManagerService == null ? BackupManager.ERROR_BACKUP_NOT_ALLOWED - : userBackupManagerService.requestBackup(packages, observer, monitor, flags); + : userBackupManagerService.requestBackup(packages, observer, monitor, flags, + operationType); } @Override diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index faec95f142eb..136cd22fad83 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -127,7 +127,6 @@ import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.ActiveRestoreSession; import com.android.server.backup.restore.PerformUnifiedRestoreTask; import com.android.server.backup.transport.TransportClient; -import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.backup.utils.BackupEligibilityRules; import com.android.server.backup.utils.BackupManagerMonitorUtils; @@ -1861,10 +1860,19 @@ public class UserBackupManagerService { /** * Requests a backup for the inputted {@code packages} with a specified {@link - * IBackupManagerMonitor} and {@link OperationType}. + * IBackupManagerMonitor}. */ public int requestBackup(String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, int flags) { + return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP); + } + + /** + * Requests a backup for the inputted {@code packages} with a specified {@link + * IBackupManagerMonitor} and {@link OperationType}. + */ + public int requestBackup(String[] packages, IBackupObserver observer, + IBackupManagerMonitor monitor, int flags, @OperationType int operationType) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup"); if (packages == null || packages.length < 1) { @@ -1895,16 +1903,13 @@ public class UserBackupManagerService { final TransportClient transportClient; final String transportDirName; - int operationType; try { transportDirName = mTransportManager.getTransportDirName( mTransportManager.getCurrentTransportName()); transportClient = mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()"); - operationType = getOperationTypeFromTransport(transportClient); - } catch (TransportNotRegisteredException | TransportNotAvailableException - | RemoteException e) { + } catch (TransportNotRegisteredException e) { BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); monitor = BackupManagerMonitorUtils.monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, @@ -4019,13 +4024,15 @@ public class UserBackupManagerService { } /** Hand off a restore session. */ - public IRestoreSession beginRestoreSession(String packageName, String transport) { + public IRestoreSession beginRestoreSession(String packageName, String transport, + @OperationType int operationType) { if (DEBUG) { Slog.v( TAG, addUserIdToLogMessage( mUserId, - "beginRestoreSession: pkg=" + packageName + " transport=" + transport)); + "beginRestoreSession: pkg=" + packageName + " transport=" + transport + + "operationType=" + operationType)); } boolean needPermission = true; @@ -4066,17 +4073,6 @@ public class UserBackupManagerService { } } - int operationType; - try { - operationType = getOperationTypeFromTransport( - mTransportManager.getTransportClientOrThrow(transport, /* caller */ - "BMS.beginRestoreSession")); - } catch (TransportNotAvailableException | TransportNotRegisteredException - | RemoteException e) { - Slog.w(TAG, "Failed to get operation type from transport: " + e); - return null; - } - synchronized (this) { if (mActiveRestoreSession != null) { Slog.i( @@ -4360,23 +4356,6 @@ public class UserBackupManagerService { } } - @VisibleForTesting - @OperationType int getOperationTypeFromTransport(TransportClient transportClient) - throws TransportNotAvailableException, RemoteException { - long oldCallingId = Binder.clearCallingIdentity(); - try { - IBackupTransport transport = transportClient.connectOrThrow( - /* caller */ "BMS.getOperationTypeFromTransport"); - if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) { - return OperationType.MIGRATION; - } else { - return OperationType.BACKUP; - } - } finally { - Binder.restoreCallingIdentity(oldCallingId); - } - } - private static String addUserIdToLogMessage(int userId, String message) { return "[UserID:" + userId + "] " + message; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 25890b056c33..542a260a5130 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -81,8 +81,6 @@ java_library_static { ":dumpstate_aidl", ":framework_native_aidl", ":gsiservice_aidl", - ":idmap2_aidl", - ":idmap2_core_aidl", ":inputconstants_aidl", ":installd_aidl", ":storaged_aidl", @@ -230,8 +228,5 @@ filegroup { "java/com/android/server/connectivity/QosCallbackAgentConnection.java", "java/com/android/server/connectivity/QosCallbackTracker.java", "java/com/android/server/connectivity/TcpKeepaliveController.java", - "java/com/android/server/connectivity/Vpn.java", - "java/com/android/server/connectivity/VpnIkev2Utils.java", - "java/com/android/server/net/LockdownVpnTracker.java", ], } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 277152d82c34..558fbc25d7df 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -16,7 +16,6 @@ package com.android.server; -import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; @@ -45,8 +44,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -95,6 +95,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; @@ -138,7 +139,6 @@ import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; -import android.net.VpnService; import android.net.VpnTransportInfo; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; @@ -170,8 +170,6 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.security.Credentials; -import android.security.KeyStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; @@ -187,9 +185,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; @@ -214,9 +209,7 @@ import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; -import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; -import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.utils.PriorityDump; @@ -309,18 +302,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final PerUidCounter mNetworkRequestCounter; - private KeyStore mKeyStore; - - @VisibleForTesting - @GuardedBy("mVpns") - protected final SparseArray<Vpn> mVpns = new SparseArray<>(); - - // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by - // a direct call to LockdownVpnTracker.isEnabled(). - @GuardedBy("mVpns") - private boolean mLockdownEnabled; - @GuardedBy("mVpns") - private LockdownVpnTracker mLockdownTracker; + private volatile boolean mLockdownEnabled; /** * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal @@ -571,6 +553,12 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47; /** + * used internally when setting the default networks for OemNetworkPreferences. + * obj = OemNetworkPreferences + */ + private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -755,6 +743,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying + // network type, to preserve previous behaviour. + private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) { + if (vpnNai != mService.getLegacyLockdownNai()) return; + + if (vpnNai.declaredUnderlyingNetworks == null + || vpnNai.declaredUnderlyingNetworks.length != 1) { + Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: " + + Arrays.toString(vpnNai.declaredUnderlyingNetworks)); + return; + } + final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork( + vpnNai.declaredUnderlyingNetworks[0]); + if (underlyingNai == null) return; + + final int type = underlyingNai.networkInfo.getType(); + final DetailedState state = DetailedState.CONNECTED; + maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */); + mService.sendLegacyNetworkBroadcast(underlyingNai, state, type); + } + /** Adds the given network to the specified legacy type list. */ public void add(int type, NetworkAgentInfo nai) { if (!isTypeSupported(type)) { @@ -772,9 +781,17 @@ public class ConnectivityService extends IConnectivityManager.Stub // Send a broadcast if this is the first network of its type or if it's the default. final boolean isDefaultNetwork = mService.isDefaultNetwork(nai); + + // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts + // to preserve previous behaviour. + final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED); if ((list.size() == 1) || isDefaultNetwork) { - maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork); - mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type); + maybeLogBroadcast(nai, state, type, isDefaultNetwork); + mService.sendLegacyNetworkBroadcast(nai, state, type); + } + + if (type == TYPE_VPN && state == DetailedState.CONNECTED) { + maybeSendLegacyLockdownBroadcast(nai); } } @@ -969,13 +986,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Get a reference to the system keystore. - */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); - } - - /** * @see ProxyTracker */ public ProxyTracker makeProxyTracker(@NonNull Context context, @@ -1039,10 +1049,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mMetricsLog = logger; mNetworkRanker = new NetworkRanker(); - final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport( - -1, NetworkRequest.Type.REQUEST); - mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder(), - null /* attributionTag */); + final NetworkRequest defaultInternetRequest = createDefaultRequest(); + mDefaultRequest = new NetworkRequestInfo( + defaultInternetRequest, null, new Binder(), + null /* attributionTags */); mNetworkRequests.put(defaultInternetRequest, mDefaultRequest); mDefaultNetworkRequests.add(mDefaultRequest); mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest); @@ -1084,7 +1094,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler); mNetd = netd; - mKeyStore = mDeps.getKeyStore(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mLocationPermissionChecker = new LocationPermissionChecker(mContext); @@ -1173,43 +1182,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mPermissionMonitor = new PermissionMonitor(mContext, mNetd); - // Set up the listener for user state for creating user VPNs. + // Listen for user add/removes to inform PermissionMonitor. // Should run on mHandler to avoid any races. IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_STARTED); - intentFilter.addAction(Intent.ACTION_USER_STOPPED); intentFilter.addAction(Intent.ACTION_USER_ADDED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); - intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( - mUserPresentReceiver, - new IntentFilter(Intent.ACTION_USER_PRESENT), - null /* broadcastPermission */, - null /* scheduler */); - - // Listen to package add and removal events for all users. - intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - - // Listen to lockdown VPN reset. - intentFilter = new IntentFilter(); - intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); - mUserAllContext.registerReceiver( - mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + mUserAllContext.registerReceiver(mIntentReceiver, intentFilter, + null /* broadcastPermission */, mHandler); mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS); @@ -1250,21 +1231,29 @@ public class ConnectivityService extends IConnectivityManager.Stub private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); - netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.removeCapability(NET_CAPABILITY_NOT_VPN); netCap.setSingleUid(uid); return netCap; } + private NetworkRequest createDefaultRequest() { + return createDefaultInternetRequestForTransport( + TYPE_NONE, NetworkRequest.Type.REQUEST); + } + private NetworkRequest createDefaultInternetRequestForTransport( int transportType, NetworkRequest.Type type) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); - netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); - if (transportType > -1) { + if (transportType > TYPE_NONE) { netCap.addTransportType(transportType); } + return createNetworkRequest(type, netCap); + } + + private NetworkRequest createNetworkRequest( + NetworkRequest.Type type, NetworkCapabilities netCap) { return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type); } @@ -1314,7 +1303,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (enable) { handleRegisterNetworkRequest(new NetworkRequestInfo( - null, networkRequest, new Binder(), null /* attributionTag */)); + networkRequest, null, new Binder(), + null /* attributionTags */)); } else { handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID, /* callOnUnavailable */ false); @@ -1387,9 +1377,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - synchronized (mVpns) { - if (mLockdownEnabled) return null; - } + if (mLockdownEnabled) return null; final NetworkAgentInfo nai = getVpnForUid(uid); if (nai != null) return nai.declaredUnderlyingNetworks; return null; @@ -1474,11 +1462,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) { networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } - synchronized (mVpns) { - if (mLockdownTracker != null) { - mLockdownTracker.augmentNetworkInfo(networkInfo); - } - } + networkInfo.setDetailedState( + getLegacyLockdownState(networkInfo.getDetailedState()), + "" /* reason */, null /* extraInfo */); } /** @@ -1537,14 +1523,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai.network; } - // Public because it's used by mLockdownTracker. - public NetworkInfo getActiveNetworkInfoUnfiltered() { - enforceAccessPermission(); - final int uid = mDeps.getCallingUid(); - NetworkState state = getUnfilteredActiveNetworkState(uid); - return state.networkInfo; - } - @Override public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { NetworkStack.checkNetworkStackPermission(mContext); @@ -2166,22 +2144,6 @@ public class ConnectivityService extends IConnectivityManager.Stub isBackgroundRestricted); } - /** - * Require that the caller is either in the same user or has appropriate permission to interact - * across users. - * - * @param userId Target user for whatever operation the current IPC is supposed to perform. - */ - private void enforceCrossUserPermission(int userId) { - if (userId == UserHandle.getCallingUserId()) { - // Not a cross-user call. - return; - } - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "ConnectivityService"); - } - private boolean checkAnyPermissionOf(String... permissions) { for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { @@ -2262,12 +2224,6 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid); } - private void enforceControlAlwaysOnVpnPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, - "ConnectivityService"); - } - private void enforceNetworkStackOrSettingsPermission() { enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -2292,6 +2248,12 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } + private void enforceOemNetworkPreferencesPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE, + "ConnectivityService"); + } + private boolean checkNetworkStackPermission() { return checkAnyPermissionOf( android.Manifest.permission.NETWORK_STACK, @@ -2340,13 +2302,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - info = new NetworkInfo(info); - mLockdownTracker.augmentNetworkInfo(info); - } - } - Intent intent = new Intent(bcastType); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info)); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); @@ -2440,10 +2395,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait - // for user to unlock device too. - updateLockdownVpn(); - // Create network requests for always-on networks. mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS)); } @@ -2634,6 +2585,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } pw.println(); + pw.print("Current per-app default networks: "); + pw.increaseIndent(); + dumpPerAppNetworkPreferences(pw); + pw.decreaseIndent(); + pw.println(); + pw.println("Current Networks:"); pw.increaseIndent(); dumpNetworks(pw); @@ -2754,6 +2711,40 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void dumpPerAppNetworkPreferences(IndentingPrintWriter pw) { + pw.println("Per-App Network Preference:"); + pw.increaseIndent(); + if (0 == mOemNetworkPreferences.getNetworkPreferences().size()) { + pw.println("none"); + } else { + pw.println(mOemNetworkPreferences.toString()); + } + pw.decreaseIndent(); + + for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) { + if (mDefaultRequest == defaultRequest) { + continue; + } + + final boolean isActive = null != defaultRequest.getSatisfier(); + pw.println("Is per-app network active:"); + pw.increaseIndent(); + pw.println(isActive); + if (isActive) { + pw.println("Active network: " + defaultRequest.getSatisfier().network.netId); + } + pw.println("Tracked UIDs:"); + pw.increaseIndent(); + if (0 == defaultRequest.mRequests.size()) { + pw.println("none, this should never occur."); + } else { + pw.println(defaultRequest.mRequests.get(0).networkCapabilities.getUids()); + } + pw.decreaseIndent(); + pw.decreaseIndent(); + } + } + private void dumpNetworkRequests(IndentingPrintWriter pw) { for (NetworkRequestInfo nri : requestsSortedById()) { pw.println(nri.toString()); @@ -2887,7 +2878,15 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, "Non-virtual networks cannot have underlying networks"); break; } + final List<Network> underlying = (List<Network>) arg.second; + + if (isLegacyLockdownNai(nai) + && (underlying == null || underlying.size() != 1)) { + Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString() + + " must have exactly one underlying network: " + underlying); + } + final Network[] oldUnderlying = nai.declaredUnderlyingNetworks; nai.declaredUnderlyingNetworks = (underlying != null) ? underlying.toArray(new Network[0]) : null; @@ -3496,7 +3495,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // incorrect) behavior. mNetworkActivityTracker.updateDataActivityTracking( null /* newNetwork */, nai); - notifyLockdownVpn(nai); ensureNetworkTransitionWakelock(nai.toShortString()); } } @@ -3586,29 +3584,38 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) { + handleRegisterNetworkRequest(Collections.singletonList(nri)); + } + + private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) { ensureRunningOnConnectivityServiceThread(); - mNetworkRequestInfoLogs.log("REGISTER " + nri); - for (final NetworkRequest req : nri.mRequests) { - mNetworkRequests.put(req, nri); - if (req.isListen()) { - for (final NetworkAgentInfo network : mNetworkAgentInfos) { - if (req.networkCapabilities.hasSignalStrength() - && network.satisfiesImmutableCapabilitiesOf(req)) { - updateSignalStrengthThresholds(network, "REGISTER", req); + for (final NetworkRequestInfo nri : nris) { + mNetworkRequestInfoLogs.log("REGISTER " + nri); + for (final NetworkRequest req : nri.mRequests) { + mNetworkRequests.put(req, nri); + if (req.isListen()) { + for (final NetworkAgentInfo network : mNetworkAgentInfos) { + if (req.networkCapabilities.hasSignalStrength() + && network.satisfiesImmutableCapabilitiesOf(req)) { + updateSignalStrengthThresholds(network, "REGISTER", req); + } } } } } + rematchAllNetworksAndRequests(); - // If the nri is satisfied, return as its score has already been sent if needed. - if (nri.isBeingSatisfied()) { - return; - } + for (final NetworkRequestInfo nri : nris) { + // If the nri is satisfied, return as its score has already been sent if needed. + if (nri.isBeingSatisfied()) { + return; + } - // As this request was not satisfied on rematch and thus never had any scores sent to the - // factories, send null now for each request of type REQUEST. - for (final NetworkRequest req : nri.mRequests) { - if (req.isRequest()) sendUpdatedScoreToFactories(req, null); + // As this request was not satisfied on rematch and thus never had any scores sent to + // the factories, send null now for each request of type REQUEST. + for (final NetworkRequest req : nri.mRequests) { + if (req.isRequest()) sendUpdatedScoreToFactories(req, null); + } } } @@ -3781,6 +3788,7 @@ public class ConnectivityService extends IConnectivityManager.Stub removeListenRequestFromNetworks(req); } } + mDefaultNetworkRequests.remove(nri); mNetworkRequestCounter.decrementCount(nri.mUid); mNetworkRequestInfoLogs.log("RELEASE " + nri); @@ -4419,6 +4427,16 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_SET_REQUIRE_VPN_FOR_UIDS: handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); break; + case EVENT_SET_OEM_NETWORK_PREFERENCE: + final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg = + (Pair<OemNetworkPreferences, + IOnSetOemNetworkPreferenceListener>) msg.obj; + try { + handleSetOemNetworkPreference(arg.first, arg.second); + } catch (RemoteException e) { + loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e); + } + break; } } } @@ -4721,183 +4739,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Prepare for a VPN application. - * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, - * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param oldPackage Package name of the application which currently controls VPN, which will - * be replaced. If there is no such application, this should should either be - * {@code null} or {@link VpnConfig.LEGACY_VPN}. - * @param newPackage Package name of the application which should gain control of VPN, or - * {@code null} to disable. - * @param userId User for whom to prepare the new VPN. - * - * @hide - */ - @Override - public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, - int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - throwIfLockdownEnabled(); - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); - } else { - return false; - } - } - } - - /** - * Set whether the VPN package has the ability to launch VPNs without user intervention. This - * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} - * class. If the caller is not {@code userId}, {@link - * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param packageName The package for which authorization state should change. - * @param userId User for whom {@code packageName} is installed. - * @param authorized {@code true} if this app should be able to start a VPN connection without - * explicit user approval, {@code false} if not. - * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN - * permissions should be granted. When unauthorizing an app, {@link - * VpnManager.TYPE_VPN_NONE} should be used. - * @hide - */ - @Override - public void setVpnPackageAuthorization( - String packageName, int userId, @VpnManager.VpnType int vpnType) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - vpn.setPackageAuthorization(packageName, vpnType); - } - } - } - - /** - * Configure a TUN interface and return its file descriptor. Parameters - * are encoded and opaque to this class. This method is used by VpnBuilder - * and not available in ConnectivityManager. Permissions are checked in - * Vpn class. - * @hide - */ - @Override - public ParcelFileDescriptor establishVpn(VpnConfig config) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).establish(config); - } - } - - /** - * Stores the given VPN profile based on the provisioning package name. - * - * <p>If there is already a VPN profile stored for the provisioning package, this call will - * overwrite the profile. - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @return {@code true} if user consent has already been granted, {@code false} otherwise. - * @hide - */ - @Override - public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); - } - } - - /** - * Deletes the stored VPN profile for the provisioning package - * - * <p>If there are no profiles for the given package, this method will silently succeed. - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void deleteVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); - } - } - - /** - * Starts the VPN based on the stored profile for the given package - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @throws IllegalArgumentException if no profile was found for the given package name. - * @hide - */ - @Override - public void startVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startVpnProfile(packageName, mKeyStore); - } - } - - /** - * Stops the Platform VPN if the provided package is running one. - * - * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void stopVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).stopVpnProfile(packageName); - } - } - - /** - * Start legacy VPN, controlling native daemons as needed. Creates a - * secondary thread to perform connection work, returning quickly. - */ - @Override - public void startLegacyVpn(VpnProfile profile) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final LinkProperties egress = getActiveLinkProperties(); - if (egress == null) { - throw new IllegalStateException("Missing active network connection"); - } - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); - } - } - - /** - * Return the information of the ongoing legacy VPN. This method is used - * by VpnSettings and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - */ - @Override - public LegacyVpnInfo getLegacyVpnInfo(int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); - } - } - - /** * Return the information of all ongoing VPNs. * * <p>This method is used to update NetworkStatsService. @@ -4906,10 +4747,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private UnderlyingNetworkInfo[] getAllVpnInfo() { ensureRunningOnConnectivityServiceThread(); - synchronized (mVpns) { - if (mLockdownEnabled) { - return new UnderlyingNetworkInfo[0]; - } + if (mLockdownEnabled) { + return new UnderlyingNetworkInfo[0]; } List<UnderlyingNetworkInfo> infoList = new ArrayList<>(); for (NetworkAgentInfo nai : mNetworkAgentInfos) { @@ -4965,25 +4804,6 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.linkProperties.getInterfaceName(), interfaces); } - /** - * Returns the information of the ongoing VPN for {@code userId}. This method is used by - * VpnDialogs and not available in ConnectivityManager. - * Permissions are checked in Vpn class. - * @hide - */ - @Override - public VpnConfig getVpnConfig(int userId) { - enforceCrossUserPermission(userId); - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.getVpnConfig(); - } else { - return null; - } - } - } - // TODO This needs to be the default network that applies to the NAI. private Network[] underlyingNetworksOrDefault(final int ownerUid, Network[] underlyingNetworks) { @@ -5071,195 +4891,54 @@ public class ConnectivityService extends IConnectivityManager.Stub mVpnBlockedUidRanges = newVpnBlockedUidRanges; } - private boolean isLockdownVpnEnabled() { - return mKeyStore.contains(Credentials.LOCKDOWN_VPN); - } - @Override - public boolean updateLockdownVpn() { - // Allow the system UID for the system server and for Settings. - // Also, for unit tests, allow the process that ConnectivityService is running in. - if (mDeps.getCallingUid() != Process.SYSTEM_UID - && Binder.getCallingPid() != Process.myPid()) { - logw("Lockdown VPN only available to system process or AID_SYSTEM"); - return false; - } - - synchronized (mVpns) { - // Tear down existing lockdown if profile was removed - mLockdownEnabled = isLockdownVpnEnabled(); - if (mLockdownEnabled) { - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - loge("Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - loge("Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(mDeps.getCallingUid()); - Vpn vpn = mVpns.get(user); - if (vpn == null) { - logw("VPN for user " + user + " not ready yet. Skipping lockdown"); - return false; - } - setLockdownTracker( - new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn, profile)); - } else { - setLockdownTracker(null); - } - } - - return true; - } - - /** - * Internally set new {@link LockdownVpnTracker}, shutting down any existing - * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. - */ - @GuardedBy("mVpns") - private void setLockdownTracker(LockdownVpnTracker tracker) { - // Shutdown any existing tracker - final LockdownVpnTracker existing = mLockdownTracker; - // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the - // necessary onBlockedStatusChanged callbacks. - mLockdownTracker = null; - if (existing != null) { - existing.shutdown(); - } - - if (tracker != null) { - mLockdownTracker = tracker; - mLockdownTracker.init(); - } - } - - /** - * Throws if there is any currently running, always-on Legacy VPN. - * - * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is - * running across the entire system. Tracking for app-based VPNs is done on a per-user, - * per-package basis in Vpn.java - */ - @GuardedBy("mVpns") - private void throwIfLockdownEnabled() { - if (mLockdownEnabled) { - throw new IllegalStateException("Unavailable in lockdown mode"); - } + public void setLegacyLockdownVpnEnabled(boolean enabled) { + enforceSettingsPermission(); + mHandler.post(() -> mLockdownEnabled = enabled); } - /** - * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform - * some setup and then call {@code establish()} to connect. - * - * @return {@code true} if the service was started, the service was already connected, or there - * was no always-on VPN to start. {@code false} otherwise. - */ - private boolean startAlwaysOnVpn(int userId) { - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - // Shouldn't happen as all code paths that point here should have checked the Vpn - // exists already. - Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); - return false; - } - - return vpn.startAlwaysOnVpn(mKeyStore); - } + private boolean isLegacyLockdownNai(NetworkAgentInfo nai) { + return mLockdownEnabled + && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY + && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID); } - @Override - public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { - enforceSettingsPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + private NetworkAgentInfo getLegacyLockdownNai() { + if (!mLockdownEnabled) { + return null; } - } - - @Override - public boolean setAlwaysOnVpnPackage( - int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (isLockdownVpnEnabled()) { - return false; - } + // The legacy lockdown VPN always only applies to userId 0. + final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID); + if (nai == null || !isLegacyLockdownNai(nai)) return null; - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) { - return false; - } - if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - return false; - } + // The legacy lockdown VPN must always have exactly one underlying network. + // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in + // a local variable. There is no need to make a copy because its contents cannot change. + final Network[] underlying = nai.declaredUnderlyingNetworks; + if (underlying == null || underlying.length != 1) { + return null; } - return true; - } - @Override - public String getAlwaysOnVpnPackage(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getAlwaysOnPackage(); + // The legacy lockdown VPN always uses the default network. + // If the VPN's underlying network is no longer the current default network, it means that + // the default network has just switched, and the VPN is about to disconnect. + // Report that the VPN is not connected, so when the state of NetworkInfo objects + // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED. + final NetworkAgentInfo defaultNetwork = getDefaultNetwork(); + if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) { + return null; } - } - @Override - public boolean isVpnLockdownEnabled(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.getLockdown(); - } - } + return nai; + }; - @Override - public List<String> getVpnLockdownWhitelist(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getLockdownAllowlist(); + private DetailedState getLegacyLockdownState(DetailedState origState) { + if (origState != DetailedState.CONNECTED) { + return origState; } + return (mLockdownEnabled && getLegacyLockdownNai() == null) + ? DetailedState.CONNECTING + : DetailedState.CONNECTED; } @Override @@ -5294,111 +4973,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void onUserStarted(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn != null) { - loge("Starting user already has a VPN"); - return; - } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); - mVpns.put(userId, userVpn); - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } - } - } - - private void onUserStopped(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn == null) { - loge("Stopped user has no VPN"); - return; - } - userVpn.onUserStopped(); - mVpns.delete(userId); - } - } - private void onUserAdded(int userId) { mPermissionMonitor.onUserAdded(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserAdded(userId); - } - } } private void onUserRemoved(int userId) { mPermissionMonitor.onUserRemoved(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserRemoved(userId); - } - } - } - - private void onPackageReplaced(String packageName, int uid) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); - return; - } - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { - log("Restarting always-on VPN package " + packageName + " for user " - + userId); - vpn.startAlwaysOnVpn(mKeyStore); - } - } - } - - private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); - return; - } - - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { - log("Removing always-on VPN package " + packageName + " for user " - + userId); - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - } - } - } - - private void onUserUnlocked(int userId) { - synchronized (mVpns) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); - } - } - } - - private void onVpnLockdownReset() { - synchronized (mVpns) { - if (mLockdownTracker != null) mLockdownTracker.reset(); - } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -5407,52 +4987,20 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final Uri packageData = intent.getData(); - final String packageName = - packageData != null ? packageData.getSchemeSpecificPart() : null; - - if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { - onVpnLockdownReset(); - } // UserId should be filled for below intents, check the existence. if (userId == UserHandle.USER_NULL) return; - if (Intent.ACTION_USER_STARTED.equals(action)) { - onUserStarted(userId); - } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - onUserStopped(userId); - } else if (Intent.ACTION_USER_ADDED.equals(action)) { + if (Intent.ACTION_USER_ADDED.equals(action)) { onUserAdded(userId); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { onUserRemoved(userId); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - onUserUnlocked(userId); - } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { - onPackageReplaced(packageName, uid); - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - final boolean isReplacing = intent.getBooleanExtra( - Intent.EXTRA_REPLACING, false); - onPackageRemoved(packageName, uid, isReplacing); - } else { + } else { Log.wtf(TAG, "received unexpected intent: " + action); } } }; - private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Try creating lockdown tracker, since user present usually means - // unlocked keystore. - updateLockdownVpn(); - // Use the same context that registered receiver before to unregister it. Because use - // different context to unregister receiver will cause exception. - context.unregisterReceiver(this); - } - }; - private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>(); @@ -5551,10 +5099,12 @@ public class ConnectivityService extends IConnectivityManager.Stub final PendingIntent mPendingIntent; boolean mPendingIntentSent; + @Nullable + final Messenger mMessenger; + @Nullable private final IBinder mBinder; final int mPid; final int mUid; - final Messenger messenger; @Nullable final String mCallingAttributionTag; @@ -5570,12 +5120,17 @@ public class ConnectivityService extends IConnectivityManager.Stub return uids; } - NetworkRequestInfo(NetworkRequest r, PendingIntent pi, + NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { + this(Collections.singletonList(r), pi, callingAttributionTag); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); mPendingIntent = pi; - messenger = null; + mMessenger = null; mBinder = null; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); @@ -5583,11 +5138,16 @@ public class ConnectivityService extends IConnectivityManager.Stub mCallingAttributionTag = callingAttributionTag; } - NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, - @Nullable String callingAttributionTag) { + NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, + @Nullable final IBinder binder, @Nullable String callingAttributionTag) { + this(Collections.singletonList(r), m, binder, callingAttributionTag); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m, + @Nullable final IBinder binder, @Nullable String callingAttributionTag) { super(); - messenger = m; mRequests = initializeRequests(r); + mMessenger = m; ensureAllNetworkRequestsHaveType(mRequests); mBinder = binder; mPid = getCallingPid(); @@ -5603,7 +5163,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - NetworkRequestInfo(NetworkRequest r) { + NetworkRequestInfo(@NonNull final NetworkRequest r) { + this(Collections.singletonList(r)); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r) { this(r, null /* pi */, null /* callingAttributionTag */); } @@ -5618,9 +5182,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return mRequests.size() > 1; } - private List<NetworkRequest> initializeRequests(NetworkRequest r) { - final ArrayList<NetworkRequest> tempRequests = new ArrayList<>(); - tempRequests.add(new NetworkRequest(r)); + private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) { + // Creating a defensive copy to prevent the sender from modifying the list being + // reflected in the return value of this method. + final List<NetworkRequest> tempRequests = new ArrayList<>(r); return Collections.unmodifiableList(tempRequests); } @@ -5804,7 +5369,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); NetworkRequestInfo nri = - new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag); + new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); if (DBG) log("requestNetwork for " + nri); // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were @@ -5813,7 +5378,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // changes don't alter request matching. if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT && (!networkCapabilities.equalRequestableCapabilities(defaultNc))) { - Log.wtf(TAG, "TRACK_SYSTEM_DEFAULT capabilities don't match default request: " + throw new IllegalStateException( + "TRACK_SYSTEM_DEFAULT capabilities don't match default request: " + networkCapabilities + " vs. " + defaultNc); } @@ -5970,7 +5536,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); NetworkRequestInfo nri = - new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag); + new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); if (VDBG) log("listenForNetwork for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); @@ -6098,13 +5664,20 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mBlockedAppUids") private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); + // Current OEM network preferences. + @NonNull + private OemNetworkPreferences mOemNetworkPreferences = + new OemNetworkPreferences.Builder().build(); + // The always-on request for an Internet-capable network that apps without a specific default // fall back to. + @VisibleForTesting @NonNull - private final NetworkRequestInfo mDefaultRequest; + final NetworkRequestInfo mDefaultRequest; // Collection of NetworkRequestInfo's used for default networks. + @VisibleForTesting @NonNull - private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>(); + final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>(); private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) { return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri); @@ -7181,7 +6754,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri, @NonNull final NetworkAgentInfo networkAgent, final int notificationType, final int arg1) { - if (nri.messenger == null) { + if (nri.mMessenger == null) { // Default request has no msgr. Also prevents callbacks from being invoked for // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks // are Type.LISTEN, but should not have NetworkCallbacks invoked. @@ -7250,7 +6823,7 @@ public class ConnectivityService extends IConnectivityManager.Stub String notification = ConnectivityManager.getCallbackName(notificationType); log("sending notification " + notification + " for " + nrForCallback); } - nri.messenger.send(msg); + nri.mMessenger.send(msg); } catch (RemoteException e) { // may occur naturally in the race of binder death. loge("RemoteException caught trying to send a callback msg for " + nrForCallback); @@ -7339,7 +6912,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); } mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); - notifyLockdownVpn(newDefaultNetwork); handleApplyDefaultProxy(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getHttpProxy() : null); updateTcpBufferSizes(null != newDefaultNetwork @@ -7797,12 +7369,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0; mLegacyTypeTracker.add( newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); - // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast - // to reflect the NetworkInfo of this new network. This broadcast has to be sent - // after the disconnect broadcasts above, but before the broadcasts sent by the - // legacy type tracker below. - // TODO : refactor this, it's too complex - notifyLockdownVpn(newDefaultNetwork); } } @@ -7860,18 +7426,6 @@ public class ConnectivityService extends IConnectivityManager.Stub sendInetConditionBroadcast(nai.networkInfo); } - private void notifyLockdownVpn(NetworkAgentInfo nai) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - if (nai != null && nai.isVPN()) { - mLockdownTracker.onVpnStateChanged(nai.networkInfo); - } else { - mLockdownTracker.onNetworkInfoChanged(); - } - } - } - } - @NonNull private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) { final NetworkInfo newInfo = new NetworkInfo(info); @@ -7910,7 +7464,6 @@ public class ConnectivityService extends IConnectivityManager.Stub oldInfo = networkAgent.networkInfo; networkAgent.networkInfo = newInfo; } - notifyLockdownVpn(networkAgent); if (DBG) { log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from " @@ -8211,34 +7764,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public boolean addVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).addAddress(address, prefixLength); - } - } - - @Override - public boolean removeVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).removeAddress(address, prefixLength); - } - } - - @Override - public boolean setUnderlyingNetworksForVpn(Network[] networks) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final boolean success; - synchronized (mVpns) { - success = mVpns.get(user).setUnderlyingNetworks(networks); - } - return success; - } - - @Override public String getCaptivePortalServerUrl() { enforceNetworkStackOrSettingsPermission(); String settingUrl = mContext.getResources().getString( @@ -8317,8 +7842,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - final int userId = UserHandle.getCallingUserId(); - final long token = Binder.clearCallingIdentity(); try { final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext); @@ -8330,44 +7853,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // Turn airplane mode off setAirplaneMode(false); - if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { - // Remove always-on package - synchronized (mVpns) { - final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); - if (alwaysOnPackage != null) { - setAlwaysOnVpnPackage(userId, null, false, null); - setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); - } - - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating - // VPN connections in the future without user intervention. - setVpnPackageAuthorization( - vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); - - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); - } - } - } - } - // restore private DNS settings to default mode (opportunistic) if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) { Settings.Global.putString(mContext.getContentResolver(), @@ -8459,25 +7944,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @GuardedBy("mVpns") - private Vpn getVpnIfOwner() { - return getVpnIfOwner(mDeps.getCallingUid()); - } - - // TODO: stop calling into Vpn.java and get this information from data in this class. - @GuardedBy("mVpns") - private Vpn getVpnIfOwner(int uid) { - final int user = UserHandle.getUserId(uid); - - final Vpn vpn = mVpns.get(user); - if (vpn == null) { - return null; - } else { - final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); - return (info == null || info.ownerUid != uid) ? null : vpn; - } - } - private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) { if (vpn == null) return VpnManager.TYPE_VPN_NONE; final TransportInfo ti = vpn.networkCapabilities.getTransportInfo(); @@ -8514,22 +7980,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return uid; } - @Override - public boolean isCallerCurrentAlwaysOnVpnApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getAlwaysOn(); - } - } - - @Override - public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getLockdown(); - } - } - /** * Returns a IBinder to a TestNetworkService. Will be lazily created as needed. * @@ -9205,9 +8655,212 @@ public class ConnectivityService extends IConnectivityManager.Stub mQosCallbackTracker.unregisterCallback(callback); } + private void enforceAutomotiveDevice() { + final boolean isAutomotiveDevice = + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + if (!isAutomotiveDevice) { + throw new UnsupportedOperationException( + "setOemNetworkPreference() is only available on automotive devices."); + } + } + + /** + * Used by automotive devices to set the network preferences used to direct traffic at an + * application level as per the given OemNetworkPreferences. An example use-case would be an + * automotive OEM wanting to provide connectivity for applications critical to the usage of a + * vehicle via a particular network. + * + * Calling this will overwrite the existing preference. + * + * @param preference {@link OemNetworkPreferences} The application network preference to be set. + * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used + * to communicate completion of setOemNetworkPreference(); + */ @Override - public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { - // TODO http://b/176495594 track multiple default networks with networkPreferences - if (DBG) log("setOemNetworkPreference() called with: " + preference.toString()); + public void setOemNetworkPreference( + @NonNull final OemNetworkPreferences preference, + @Nullable final IOnSetOemNetworkPreferenceListener listener) { + + enforceAutomotiveDevice(); + enforceOemNetworkPreferencesPermission(); + + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + validateOemNetworkPreferences(preference); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE, + new Pair<>(preference, listener))); + } + + private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) { + for (@OemNetworkPreferences.OemNetworkPreference final int pref + : preference.getNetworkPreferences().values()) { + if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) { + final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value."; + throw new IllegalArgumentException(msg); + } + } + } + + private void handleSetOemNetworkPreference( + @NonNull final OemNetworkPreferences preference, + @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException { + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + if (DBG) { + log("set OEM network preferences :" + preference.toString()); + } + final List<NetworkRequestInfo> nris = + new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); + updateDefaultNetworksForOemNetworkPreference(nris); + mOemNetworkPreferences = preference; + // TODO http://b/176496396 persist data to shared preferences. + + if (null != listener) { + listener.onComplete(); + } + } + + private void updateDefaultNetworksForOemNetworkPreference( + @NonNull final List<NetworkRequestInfo> nris) { + ensureRunningOnConnectivityServiceThread(); + clearNonDefaultNetworkAgents(); + addDefaultNetworkRequests(nris); + } + + private void clearNonDefaultNetworkAgents() { + // Copy mDefaultNetworkRequests to iterate and remove elements from it in + // handleRemoveNetworkRequest() without getting a ConcurrentModificationException. + final NetworkRequestInfo[] nris = + mDefaultNetworkRequests.toArray(new NetworkRequestInfo[0]); + for (final NetworkRequestInfo nri : nris) { + if (mDefaultRequest != nri) { + handleRemoveNetworkRequest(nri); + } + } + } + + private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) { + mDefaultNetworkRequests.addAll(nris); + handleRegisterNetworkRequest(nris); + } + + /** + * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}. + */ + @VisibleForTesting + final class OemNetworkRequestFactory { + List<NetworkRequestInfo> createNrisFromOemNetworkPreferences( + @NonNull final OemNetworkPreferences preference) { + final List<NetworkRequestInfo> nris = new ArrayList<>(); + final SparseArray<Set<Integer>> uids = + createUidsFromOemNetworkPreferences(preference); + for (int i = 0; i < uids.size(); i++) { + final int key = uids.keyAt(i); + final Set<Integer> value = uids.valueAt(i); + final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value); + // No need to add an nri without any requests. + if (0 == nri.mRequests.size()) { + continue; + } + nris.add(nri); + } + + return nris; + } + + private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences( + @NonNull final OemNetworkPreferences preference) { + final SparseArray<Set<Integer>> uids = new SparseArray<>(); + final PackageManager pm = mContext.getPackageManager(); + for (final Map.Entry<String, Integer> entry : + preference.getNetworkPreferences().entrySet()) { + @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue(); + try { + final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid; + if (!uids.contains(pref)) { + uids.put(pref, new ArraySet<>()); + } + uids.get(pref).add(uid); + } catch (PackageManager.NameNotFoundException e) { + // Although this may seem like an error scenario, it is ok that uninstalled + // packages are sent on a network preference as the system will watch for + // package installations associated with this network preference and update + // accordingly. This is done so as to minimize race conditions on app install. + // TODO b/177092163 add app install watching. + continue; + } + } + return uids; + } + + private NetworkRequestInfo createNriFromOemNetworkPreferences( + @OemNetworkPreferences.OemNetworkPreference final int preference, + @NonNull final Set<Integer> uids) { + final List<NetworkRequest> requests = new ArrayList<>(); + // Requests will ultimately be evaluated by order of insertion therefore it matters. + switch (preference) { + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createOemPaidNetworkRequest()); + requests.add(createDefaultRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createOemPaidNetworkRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY: + requests.add(createOemPaidNetworkRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY: + requests.add(createOemPrivateNetworkRequest()); + break; + default: + // This should never happen. + throw new IllegalArgumentException("createNriFromOemNetworkPreferences()" + + " called with invalid preference of " + preference); + } + + setOemNetworkRequestUids(requests, uids); + return new NetworkRequestInfo(requests); + } + + private NetworkRequest createUnmeteredNetworkRequest() { + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_VALIDATED); + return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap); + } + + private NetworkRequest createOemPaidNetworkRequest() { + // NET_CAPABILITY_OEM_PAID is a restricted capability. + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_OEM_PAID) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); + } + + private NetworkRequest createOemPrivateNetworkRequest() { + // NET_CAPABILITY_OEM_PRIVATE is a restricted capability. + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_OEM_PRIVATE) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); + } + + private NetworkCapabilities createDefaultPerAppNetCap() { + final NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); + return netCap; + } + + private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests, + @NonNull final Set<Integer> uids) { + final Set<UidRange> ranges = new ArraySet<>(); + for (final int uid : uids) { + ranges.add(new UidRange(uid, uid)); + } + for (final NetworkRequest req : requests) { + req.networkCapabilities.setUids(ranges); + } + } } } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index f648c3e146de..b48bc900aa84 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.IIpSecService; import android.net.INetd; import android.net.InetAddresses; @@ -41,6 +42,7 @@ import android.net.IpSecTransformResponse; import android.net.IpSecTunnelInterfaceResponse; import android.net.IpSecUdpEncapResponse; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.net.TrafficStats; import android.net.util.NetdService; @@ -797,9 +799,15 @@ public class IpSecService extends IIpSecService.Stub { } } - private final class TunnelInterfaceRecord extends OwnedResourceRecord { + /** + * Tracks an tunnel interface, and manages cleanup paths. + * + * <p>This class is not thread-safe, and expects that that users of this class will ensure + * synchronization and thread safety by holding the IpSecService.this instance lock + */ + @VisibleForTesting + final class TunnelInterfaceRecord extends OwnedResourceRecord { private final String mInterfaceName; - private final Network mUnderlyingNetwork; // outer addresses private final String mLocalAddress; @@ -810,6 +818,8 @@ public class IpSecService extends IIpSecService.Stub { private final int mIfId; + private Network mUnderlyingNetwork; + TunnelInterfaceRecord( int resourceId, String interfaceName, @@ -870,14 +880,22 @@ public class IpSecService extends IIpSecService.Stub { releaseNetId(mOkey); } - public String getInterfaceName() { - return mInterfaceName; + @GuardedBy("IpSecService.this") + public void setUnderlyingNetwork(Network underlyingNetwork) { + // When #applyTunnelModeTransform is called, this new underlying network will be used to + // update the output mark of the input transform. + mUnderlyingNetwork = underlyingNetwork; } + @GuardedBy("IpSecService.this") public Network getUnderlyingNetwork() { return mUnderlyingNetwork; } + public String getInterfaceName() { + return mInterfaceName; + } + /** Returns the local, outer address for the tunnelInterface */ public String getLocalAddress() { return mLocalAddress; @@ -1429,6 +1447,34 @@ public class IpSecService extends IIpSecService.Stub { } } + /** Set TunnelInterface to use a specific underlying network. */ + @Override + public synchronized void setNetworkForTunnelInterface( + int tunnelResourceId, Network underlyingNetwork, String callingPackage) { + enforceTunnelFeatureAndPermissions(callingPackage); + Objects.requireNonNull(underlyingNetwork, "No underlying network was specified"); + + final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + + // Get tunnelInterface record; if no such interface is found, will throw + // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + + final ConnectivityManager connectivityManager = + mContext.getSystemService(ConnectivityManager.class); + final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork); + if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) { + throw new IllegalArgumentException( + "Underlying network cannot be the network being exposed by this tunnel"); + } + + // It is meaningless to check if the network exists or is valid because the network might + // disconnect at any time after it passes the check. + + tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork); + } + /** * Delete a TunnelInterface that has been been allocated by and registered with the system * server diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index c6a8660d8797..e12586bfdc06 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -1,8 +1,8 @@ # Connectivity / Networking per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS -# Vibrator / Threads -per-file VibratorManagerService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com +# Threads +per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com # Zram writeback per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 41903fcd165f..67f6ec9f9a41 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -902,10 +902,13 @@ public class PackageWatchdog { if (registeredObserver != null) { Iterator<MonitoredPackage> it = failedPackages.iterator(); while (it.hasNext()) { - VersionedPackage versionedPkg = it.next().mPackage; - Slog.i(TAG, "Explicit health check failed for package " + versionedPkg); - registeredObserver.execute(versionedPkg, - PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1); + VersionedPackage versionedPkg = getVersionedPackage(it.next().getName()); + if (versionedPkg != null) { + Slog.i(TAG, + "Explicit health check failed for package " + versionedPkg); + registeredObserver.execute(versionedPkg, + PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1); + } } } } @@ -1342,11 +1345,7 @@ public class PackageWatchdog { MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) { - VersionedPackage pkg = getVersionedPackage(name); - if (pkg == null) { - return null; - } - return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs, + return new MonitoredPackage(name, durationMs, healthCheckDurationMs, hasPassedHealthCheck, mitigationCalls); } @@ -1371,7 +1370,7 @@ public class PackageWatchdog { * instances of this class. */ class MonitoredPackage { - private final VersionedPackage mPackage; + private final String mPackageName; // Times when package failures happen sorted in ascending order @GuardedBy("mLock") private final LongArrayQueue mFailureHistory = new LongArrayQueue(); @@ -1399,10 +1398,10 @@ public class PackageWatchdog { @GuardedBy("mLock") private long mHealthCheckDurationMs = Long.MAX_VALUE; - MonitoredPackage(VersionedPackage pkg, long durationMs, + MonitoredPackage(String packageName, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) { - mPackage = pkg; + mPackageName = packageName; mDurationMs = durationMs; mHealthCheckDurationMs = healthCheckDurationMs; mHasPassedHealthCheck = hasPassedHealthCheck; @@ -1556,7 +1555,7 @@ public class PackageWatchdog { /** Returns the monitored package name. */ private String getName() { - return mPackage.getPackageName(); + return mPackageName; } /** diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 1ad0176d3c5b..2f9819997257 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -939,9 +939,12 @@ class StorageManagerService extends IStorageManager.Stub if (transcodeEnabled) { LocalServices.getService(ActivityManagerInternal.class) .registerAnrController((packageName, uid) -> { - // TODO: Retrieve delay from ExternalStorageService that can check - // transcoding status - return SystemProperties.getInt("sys.fuse.transcode_anr_delay_ms", 0); + try { + return mStorageSessionController.getAnrDelayMillis(packageName, uid); + } catch (ExternalStorageServiceException e) { + Log.e(TAG, "Failed to get ANR delay for " + packageName, e); + return 0; + } }); } } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 7f638b9a55a2..915517a4b9ce 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -19,6 +19,7 @@ package com.android.server; import static android.app.UiModeManager.DEFAULT_PRIORITY; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; +import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; import static android.app.UiModeManager.PROJECTION_TYPE_NONE; @@ -82,6 +83,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; @@ -158,6 +160,7 @@ final class UiModeManagerService extends SystemService { private NotificationManager mNotificationManager; private StatusBarManager mStatusBarManager; private WindowManagerInternal mWindowManager; + private ActivityTaskManagerInternal mActivityTaskManager; private AlarmManager mAlarmManager; private PowerManager mPowerManager; @@ -366,6 +369,7 @@ final class UiModeManagerService extends SystemService { mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); mWindowManager = LocalServices.getService(WindowManagerInternal.class); + mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class); mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); TwilightManager twilightManager = getLocalService(TwilightManager.class); if (twilightManager != null) mTwilightManager = twilightManager; @@ -750,6 +754,39 @@ final class UiModeManagerService extends SystemService { } @Override + public void setApplicationNightMode(@UiModeManager.NightMode int mode) + throws RemoteException { + switch (mode) { + case UiModeManager.MODE_NIGHT_NO: + case UiModeManager.MODE_NIGHT_YES: + case UiModeManager.MODE_NIGHT_AUTO: + case UiModeManager.MODE_NIGHT_CUSTOM: + break; + default: + throw new IllegalArgumentException("Unknown mode: " + mode); + } + final int configNightMode; + switch (mode) { + case MODE_NIGHT_YES: + configNightMode = Configuration.UI_MODE_NIGHT_YES; + break; + case MODE_NIGHT_NO: + configNightMode = Configuration.UI_MODE_NIGHT_NO; + break; + default: + configNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED; + } + try { + final ActivityTaskManagerInternal.PackageConfigurationUpdater updater = + mActivityTaskManager.createPackageConfigurationUpdater(); + updater.setNightMode(configNightMode); + updater.commit(); + } catch (RemoteException e) { + throw e; + } + } + + @Override public boolean isUiModeLocked() { synchronized (mLock) { return mUiModeLocked; diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java new file mode 100644 index 000000000000..5d89bf1b1d82 --- /dev/null +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -0,0 +1,918 @@ +/* + * 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; + +import static android.Manifest.permission.NETWORK_STACK; + +import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.net.IVpnManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkStack; +import android.net.UnderlyingNetworkInfo; +import android.net.Uri; +import android.net.VpnManager; +import android.net.VpnService; +import android.net.util.NetdService; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.INetworkManagementService; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.security.Credentials; +import android.security.KeyStore; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.Vpn; +import com.android.server.net.LockdownVpnTracker; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs. + * @hide + */ +public class VpnManagerService extends IVpnManager.Stub { + private static final String TAG = VpnManagerService.class.getSimpleName(); + + @VisibleForTesting + protected final HandlerThread mHandlerThread; + private final Handler mHandler; + + private final Context mContext; + private final Context mUserAllContext; + + private final Dependencies mDeps; + + private final ConnectivityManager mCm; + private final KeyStore mKeyStore; + private final INetworkManagementService mNMS; + private final INetd mNetd; + private final UserManager mUserManager; + + @VisibleForTesting + @GuardedBy("mVpns") + protected final SparseArray<Vpn> mVpns = new SparseArray<>(); + + // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by + // a direct call to LockdownVpnTracker.isEnabled(). + @GuardedBy("mVpns") + private boolean mLockdownEnabled; + @GuardedBy("mVpns") + private LockdownVpnTracker mLockdownTracker; + + /** + * Dependencies of VpnManager, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** Returns the calling UID of an IPC. */ + public int getCallingUid() { + return Binder.getCallingUid(); + } + + /** Creates a HandlerThread to be used by this class. */ + public HandlerThread makeHandlerThread() { + return new HandlerThread("VpnManagerService"); + } + + /** Returns the KeyStore instance to be used by this class. */ + public KeyStore getKeyStore() { + return KeyStore.getInstance(); + } + + public INetd getNetd() { + return NetdService.getInstance(); + } + + public INetworkManagementService getINetworkManagementService() { + return INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + } + } + + public VpnManagerService(Context context, Dependencies deps) { + mContext = context; + mDeps = deps; + mHandlerThread = mDeps.makeHandlerThread(); + mHandlerThread.start(); + mHandler = mHandlerThread.getThreadHandler(); + mKeyStore = mDeps.getKeyStore(); + mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); + mCm = mContext.getSystemService(ConnectivityManager.class); + mNMS = mDeps.getINetworkManagementService(); + mNetd = mDeps.getNetd(); + mUserManager = mContext.getSystemService(UserManager.class); + registerReceivers(); + log("VpnManagerService starting up"); + } + + /** Creates a new VpnManagerService */ + public static VpnManagerService create(Context context) { + return new VpnManagerService(context, new Dependencies()); + } + + /** Informs the service that the system is ready. */ + public void systemReady() { + // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait + // for user to unlock device too. + updateLockdownVpn(); + } + + @Override + /** Dumps service state. */ + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, + @Nullable String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println("VPNs:"); + pw.increaseIndent(); + synchronized (mVpns) { + for (int i = 0; i < mVpns.size(); i++) { + pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage()); + } + pw.decreaseIndent(); + } + } + + /** + * Prepare for a VPN application. + * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, + * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. + * + * @param oldPackage Package name of the application which currently controls VPN, which will + * be replaced. If there is no such application, this should should either be + * {@code null} or {@link VpnConfig.LEGACY_VPN}. + * @param newPackage Package name of the application which should gain control of VPN, or + * {@code null} to disable. + * @param userId User for whom to prepare the new VPN. + * + * @hide + */ + @Override + public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, + int userId) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + throwIfLockdownEnabled(); + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); + } else { + return false; + } + } + } + + /** + * Set whether the VPN package has the ability to launch VPNs without user intervention. This + * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} + * class. If the caller is not {@code userId}, {@link + * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. + * + * @param packageName The package for which authorization state should change. + * @param userId User for whom {@code packageName} is installed. + * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN + * permissions should be granted. When unauthorizing an app, {@link + * VpnManager.TYPE_VPN_NONE} should be used. + * @hide + */ + @Override + public void setVpnPackageAuthorization( + String packageName, int userId, @VpnManager.VpnType int vpnType) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + vpn.setPackageAuthorization(packageName, vpnType); + } + } + } + + /** + * Configure a TUN interface and return its file descriptor. Parameters + * are encoded and opaque to this class. This method is used by VpnBuilder + * and not available in VpnManager. Permissions are checked in + * Vpn class. + * @hide + */ + @Override + public ParcelFileDescriptor establishVpn(VpnConfig config) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).establish(config); + } + } + + @Override + public boolean addVpnAddress(String address, int prefixLength) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).addAddress(address, prefixLength); + } + } + + @Override + public boolean removeVpnAddress(String address, int prefixLength) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).removeAddress(address, prefixLength); + } + } + + @Override + public boolean setUnderlyingNetworksForVpn(Network[] networks) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + final boolean success; + synchronized (mVpns) { + success = mVpns.get(user).setUnderlyingNetworks(networks); + } + return success; + } + + /** + * Stores the given VPN profile based on the provisioning package name. + * + * <p>If there is already a VPN profile stored for the provisioning package, this call will + * overwrite the profile. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @return {@code true} if user consent has already been granted, {@code false} otherwise. + * @hide + */ + @Override + public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); + } + } + + /** + * Deletes the stored VPN profile for the provisioning package + * + * <p>If there are no profiles for the given package, this method will silently succeed. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @hide + */ + @Override + public void deleteVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); + } + } + + /** + * Starts the VPN based on the stored profile for the given package + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @throws IllegalArgumentException if no profile was found for the given package name. + * @hide + */ + @Override + public void startVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + mVpns.get(user).startVpnProfile(packageName, mKeyStore); + } + } + + /** + * Stops the Platform VPN if the provided package is running one. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @hide + */ + @Override + public void stopVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + mVpns.get(user).stopVpnProfile(packageName); + } + } + + /** + * Start legacy VPN, controlling native daemons as needed. Creates a + * secondary thread to perform connection work, returning quickly. + */ + @Override + public void startLegacyVpn(VpnProfile profile) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + final LinkProperties egress = mCm.getActiveLinkProperties(); + if (egress == null) { + throw new IllegalStateException("Missing active network connection"); + } + synchronized (mVpns) { + throwIfLockdownEnabled(); + mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); + } + } + + /** + * Return the information of the ongoing legacy VPN. This method is used + * by VpnSettings and not available in ConnectivityManager. Permissions + * are checked in Vpn class. + */ + @Override + public LegacyVpnInfo getLegacyVpnInfo(int userId) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + return mVpns.get(userId).getLegacyVpnInfo(); + } + } + + /** + * Returns the information of the ongoing VPN for {@code userId}. This method is used by + * VpnDialogs and not available in ConnectivityManager. + * Permissions are checked in Vpn class. + * @hide + */ + @Override + public VpnConfig getVpnConfig(int userId) { + enforceCrossUserPermission(userId); + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + return vpn.getVpnConfig(); + } else { + return null; + } + } + } + + private boolean isLockdownVpnEnabled() { + return mKeyStore.contains(Credentials.LOCKDOWN_VPN); + } + + @Override + public boolean updateLockdownVpn() { + // Allow the system UID for the system server and for Settings. + // Also, for unit tests, allow the process that ConnectivityService is running in. + if (mDeps.getCallingUid() != Process.SYSTEM_UID + && Binder.getCallingPid() != Process.myPid()) { + logw("Lockdown VPN only available to system process or AID_SYSTEM"); + return false; + } + + synchronized (mVpns) { + // Tear down existing lockdown if profile was removed + mLockdownEnabled = isLockdownVpnEnabled(); + if (!mLockdownEnabled) { + setLockdownTracker(null); + return true; + } + + byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + if (profileTag == null) { + loge("Lockdown VPN configured but cannot be read from keystore"); + return false; + } + String profileName = new String(profileTag); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + if (profile == null) { + loge("Lockdown VPN configured invalid profile " + profileName); + setLockdownTracker(null); + return true; + } + int user = UserHandle.getUserId(mDeps.getCallingUid()); + Vpn vpn = mVpns.get(user); + if (vpn == null) { + logw("VPN for user " + user + " not ready yet. Skipping lockdown"); + return false; + } + setLockdownTracker( + new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); + } + + return true; + } + + /** + * Internally set new {@link LockdownVpnTracker}, shutting down any existing + * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. + */ + @GuardedBy("mVpns") + private void setLockdownTracker(LockdownVpnTracker tracker) { + // Shutdown any existing tracker + final LockdownVpnTracker existing = mLockdownTracker; + // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the + // necessary onBlockedStatusChanged callbacks. + mLockdownTracker = null; + if (existing != null) { + existing.shutdown(); + } + + if (tracker != null) { + mLockdownTracker = tracker; + mLockdownTracker.init(); + } + } + + /** + * Throws if there is any currently running, always-on Legacy VPN. + * + * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is + * running across the entire system. Tracking for app-based VPNs is done on a per-user, + * per-package basis in Vpn.java + */ + @GuardedBy("mVpns") + private void throwIfLockdownEnabled() { + if (mLockdownEnabled) { + throw new IllegalStateException("Unavailable in lockdown mode"); + } + } + + /** + * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform + * some setup and then call {@code establish()} to connect. + * + * @return {@code true} if the service was started, the service was already connected, or there + * was no always-on VPN to start. {@code false} otherwise. + */ + private boolean startAlwaysOnVpn(int userId) { + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + // Shouldn't happen as all code paths that point here should have checked the Vpn + // exists already. + Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); + return false; + } + + return vpn.startAlwaysOnVpn(mKeyStore); + } + } + + @Override + public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { + enforceSettingsPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + } + } + + @Override + public boolean setAlwaysOnVpnPackage( + int userId, String packageName, boolean lockdown, List<String> lockdownAllowlist) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + // Can't set always-on VPN if legacy VPN is already in lockdown mode. + if (isLockdownVpnEnabled()) { + return false; + } + + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) { + return false; + } + if (!startAlwaysOnVpn(userId)) { + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + return false; + } + } + return true; + } + + @Override + public String getAlwaysOnVpnPackage(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getAlwaysOnPackage(); + } + } + + @Override + public boolean isVpnLockdownEnabled(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.getLockdown(); + } + } + + @Override + public List<String> getVpnLockdownAllowlist(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getLockdownAllowlist(); + } + } + + @GuardedBy("mVpns") + private Vpn getVpnIfOwner() { + return getVpnIfOwner(mDeps.getCallingUid()); + } + + // TODO: stop calling into Vpn.java and get this information from data in this class. + @GuardedBy("mVpns") + private Vpn getVpnIfOwner(int uid) { + final int user = UserHandle.getUserId(uid); + + final Vpn vpn = mVpns.get(user); + if (vpn == null) { + return null; + } else { + final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); + return (info == null || info.ownerUid != uid) ? null : vpn; + } + } + + private void registerReceivers() { + // Set up the listener for user state for creating user VPNs. + // Should run on mHandler to avoid any races. + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_STARTED); + intentFilter.addAction(Intent.ACTION_USER_STOPPED); + intentFilter.addAction(Intent.ACTION_USER_ADDED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); + + mUserAllContext.registerReceiver( + mIntentReceiver, + intentFilter, + null /* broadcastPermission */, + mHandler); + mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( + mUserPresentReceiver, + new IntentFilter(Intent.ACTION_USER_PRESENT), + null /* broadcastPermission */, + mHandler /* scheduler */); + + // Listen to package add and removal events for all users. + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mUserAllContext.registerReceiver( + mIntentReceiver, + intentFilter, + null /* broadcastPermission */, + mHandler); + + // Listen to lockdown VPN reset. + intentFilter = new IntentFilter(); + intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); + mUserAllContext.registerReceiver( + mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + } + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ensureRunningOnHandlerThread(); + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final Uri packageData = intent.getData(); + final String packageName = + packageData != null ? packageData.getSchemeSpecificPart() : null; + + if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { + onVpnLockdownReset(); + } + + // UserId should be filled for below intents, check the existence. + if (userId == UserHandle.USER_NULL) return; + + if (Intent.ACTION_USER_STARTED.equals(action)) { + onUserStarted(userId); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + onUserStopped(userId); + } else if (Intent.ACTION_USER_ADDED.equals(action)) { + onUserAdded(userId); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(userId); + } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { + onUserUnlocked(userId); + } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { + onPackageReplaced(packageName, uid); + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + final boolean isReplacing = intent.getBooleanExtra( + Intent.EXTRA_REPLACING, false); + onPackageRemoved(packageName, uid, isReplacing); + } else { + Log.wtf(TAG, "received unexpected intent: " + action); + } + } + }; + + private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ensureRunningOnHandlerThread(); + // Try creating lockdown tracker, since user present usually means + // unlocked keystore. + updateLockdownVpn(); + // Use the same context that registered receiver before to unregister it. Because use + // different context to unregister receiver will cause exception. + context.unregisterReceiver(this); + } + }; + + private void onUserStarted(int userId) { + synchronized (mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn != null) { + loge("Starting user already has a VPN"); + return; + } + userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); + mVpns.put(userId, userVpn); + if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { + updateLockdownVpn(); + } + } + } + + private void onUserStopped(int userId) { + synchronized (mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn == null) { + loge("Stopped user has no VPN"); + return; + } + userVpn.onUserStopped(); + mVpns.delete(userId); + } + } + + @Override + public boolean isCallerCurrentAlwaysOnVpnApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getAlwaysOn(); + } + } + + @Override + public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getLockdown(); + } + } + + + private void onUserAdded(int userId) { + synchronized (mVpns) { + final int vpnsSize = mVpns.size(); + for (int i = 0; i < vpnsSize; i++) { + Vpn vpn = mVpns.valueAt(i); + vpn.onUserAdded(userId); + } + } + } + + private void onUserRemoved(int userId) { + synchronized (mVpns) { + final int vpnsSize = mVpns.size(); + for (int i = 0; i < vpnsSize; i++) { + Vpn vpn = mVpns.valueAt(i); + vpn.onUserRemoved(userId); + } + } + } + + private void onPackageReplaced(String packageName, int uid) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); + return; + } + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { + log("Restarting always-on VPN package " + packageName + " for user " + + userId); + vpn.startAlwaysOnVpn(mKeyStore); + } + } + } + + private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); + return; + } + + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { + log("Removing always-on VPN package " + packageName + " for user " + + userId); + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + } + } + } + + private void onUserUnlocked(int userId) { + synchronized (mVpns) { + // User present may be sent because of an unlock, which might mean an unlocked keystore. + if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { + updateLockdownVpn(); + } else { + startAlwaysOnVpn(userId); + } + } + } + + private void onVpnLockdownReset() { + synchronized (mVpns) { + if (mLockdownTracker != null) mLockdownTracker.reset(); + } + } + + + @Override + public void factoryReset() { + enforceSettingsPermission(); + + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET) + || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { + return; + } + + // Remove always-on package + final int userId = UserHandle.getCallingUserId(); + synchronized (mVpns) { + final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); + if (alwaysOnPackage != null) { + setAlwaysOnVpnPackage(userId, null, false, null); + setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); + } + + // Turn Always-on VPN off + if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { + final long ident = Binder.clearCallingIdentity(); + try { + mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mLockdownEnabled = false; + setLockdownTracker(null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Turn VPN off + VpnConfig vpnConfig = getVpnConfig(userId); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); + } else { + // Prevent this app (packagename = vpnConfig.user) from initiating + // VPN connections in the future without user intervention. + setVpnPackageAuthorization( + vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); + + prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + } + } + } + } + + private void ensureRunningOnHandlerThread() { + if (mHandler.getLooper().getThread() != Thread.currentThread()) { + throw new IllegalStateException( + "Not running on VpnManagerService thread: " + + Thread.currentThread().getName()); + } + } + + private void enforceControlAlwaysOnVpnPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, + "VpnManagerService"); + } + + /** + * Require that the caller is either in the same user or has appropriate permission to interact + * across users. + * + * @param userId Target user for whatever operation the current IPC is supposed to perform. + */ + private void enforceCrossUserPermission(int userId) { + if (userId == UserHandle.getCallingUserId()) { + // Not a cross-user call. + return; + } + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "VpnManagerService"); + } + + private void enforceSettingsPermission() { + enforceAnyPermissionOf(mContext, + android.Manifest.permission.NETWORK_SETTINGS, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + } + + private static void log(String s) { + Log.d(TAG, s); + } + + private static void logw(String s) { + Log.w(TAG, s); + } + + private static void loge(String s) { + Log.e(TAG, s); + } +} diff --git a/services/core/java/com/android/server/accounts/OWNERS b/services/core/java/com/android/server/accounts/OWNERS index ea5fd36702f9..8dcc04a27af6 100644 --- a/services/core/java/com/android/server/accounts/OWNERS +++ b/services/core/java/com/android/server/accounts/OWNERS @@ -3,7 +3,6 @@ dementyev@google.com sandrakwan@google.com hackbod@google.com svetoslavganov@google.com -moltmann@google.com fkupolov@google.com yamasani@google.com omakoto@google.com diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f0f29a9be7a7..26fede1eb950 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1490,7 +1490,9 @@ public class ActivityManagerService extends IActivityManager.Stub private static final int INDEX_TOTAL_SWAP_PSS = 10; private static final int INDEX_TOTAL_RSS = 11; private static final int INDEX_TOTAL_NATIVE_PSS = 12; - private static final int INDEX_LAST = 13; + private static final int INDEX_TOTAL_MEMTRACK_GRAPHICS = 13; + private static final int INDEX_TOTAL_MEMTRACK_GL = 14; + private static final int INDEX_LAST = 15; final class UiHandler extends Handler { public UiHandler() { @@ -10316,6 +10318,7 @@ public class ActivityManagerService extends IActivityManager.Stub long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; + long[] memtrackTmp = new long[4]; long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length]; @@ -10349,6 +10352,8 @@ public class ActivityManagerService extends IActivityManager.Stub final int reportType; final long startTime; final long endTime; + long memtrackGraphics = 0; + long memtrackGl = 0; if (opts.dumpDetails || (!brief && !opts.oomOnly)) { reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW; startTime = SystemClock.currentThreadTimeMillis(); @@ -10360,7 +10365,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else { reportType = ProcessStats.ADD_PSS_EXTERNAL; startTime = SystemClock.currentThreadTimeMillis(); - long pss = Debug.getPss(pid, tmpLong, null); + long pss = Debug.getPss(pid, tmpLong, memtrackTmp); if (pss == 0) { continue; } @@ -10368,6 +10373,8 @@ public class ActivityManagerService extends IActivityManager.Stub endTime = SystemClock.currentThreadTimeMillis(); mi.dalvikPrivateDirty = (int) tmpLong[0]; mi.dalvikRss = (int) tmpLong[2]; + memtrackGraphics = memtrackTmp[1]; + memtrackGl = memtrackTmp[2]; } if (!opts.isCheckinRequest && opts.dumpDetails) { pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **"); @@ -10431,6 +10438,8 @@ public class ActivityManagerService extends IActivityManager.Stub ss[INDEX_TOTAL_PSS] += myTotalPss; ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss; ss[INDEX_TOTAL_RSS] += myTotalRss; + ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics; + ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl; MemItem pssItem = new MemItem(r.processName + " (pid " + pid + (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss, myTotalSwapPss, myTotalRss, pid, hasActivities); @@ -10494,6 +10503,8 @@ public class ActivityManagerService extends IActivityManager.Stub final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1]; mAppProfiler.forAllCpuStats((st) -> { if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { + long memtrackGraphics = 0; + long memtrackGl = 0; if (memInfos[0] == null) { memInfos[0] = new Debug.MemoryInfo(); } @@ -10503,13 +10514,15 @@ public class ActivityManagerService extends IActivityManager.Stub return; } } else { - long pss = Debug.getPss(st.pid, tmpLong, null); + long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp); if (pss == 0) { return; } info.nativePss = (int) pss; info.nativePrivateDirty = (int) tmpLong[0]; info.nativeRss = (int) tmpLong[2]; + memtrackGraphics = memtrackTmp[1]; + memtrackGl = memtrackTmp[2]; } final long myTotalPss = info.getTotalPss(); @@ -10519,6 +10532,8 @@ public class ActivityManagerService extends IActivityManager.Stub ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss; ss[INDEX_TOTAL_RSS] += myTotalRss; ss[INDEX_TOTAL_NATIVE_PSS] += myTotalPss; + ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics; + ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl; MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss, @@ -10726,8 +10741,21 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print(" mapped + "); pw.print(stringifyKBSize(dmabufUnmapped)); pw.println(" unmapped)"); - kernelUsed += totalExportedDmabuf; + // Account unmapped dmabufs as part of kernel memory allocations + kernelUsed += dmabufUnmapped; + // Replace memtrack HAL reported Graphics category with mapped dmabufs + ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS]; + ss[INDEX_TOTAL_PSS] += dmabufMapped; + } + + // totalDmabufHeapExported is included in totalExportedDmabuf above and hence do not + // need to be added to kernelUsed. + final long totalDmabufHeapExported = Debug.getDmabufHeapTotalExportedKb(); + if (totalDmabufHeapExported >= 0) { + pw.print("DMA-BUF Heaps: "); + pw.println(stringifyKBSize(totalDmabufHeapExported)); } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); if (totalDmabufHeapPool >= 0) { pw.print("DMA-BUF Heaps pool: "); @@ -10736,13 +10764,27 @@ public class ActivityManagerService extends IActivityManager.Stub } final long gpuUsage = Debug.getGpuTotalUsageKb(); if (gpuUsage >= 0) { - pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage)); + final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb(); + if (gpuDmaBufUsage >= 0) { + final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage; + pw.print(" GPU: "); + pw.print(stringifyKBSize(gpuUsage)); + pw.print(" ("); + pw.print(stringifyKBSize(gpuDmaBufUsage)); + pw.print(" dmabuf + "); + pw.print(stringifyKBSize(gpuPrivateUsage)); + pw.println(" private)"); + // Replace memtrack HAL reported GL category with private GPU allocations and + // account it as part of kernel memory allocations + ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GL]; + kernelUsed += gpuPrivateUsage; + } else { + pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage)); + } } - /* - * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of - * memInfo.getCachedSizeKb(). - */ + // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of + // memInfo.getCachedSizeKb(). final long lostRAM = memInfo.getTotalSizeKb() - (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS]) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 3ff58729f807..c8630fa52973 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1322,7 +1322,7 @@ public class AppProfiler { infoMap.put(mi.pid, mi); } updateCpuStatsNow(); - long[] memtrackTmp = new long[1]; + long[] memtrackTmp = new long[4]; long[] swaptrackTmp = new long[2]; // Get a list of Stats that have vsize > 0 final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0); @@ -1345,6 +1345,8 @@ public class AppProfiler { long totalPss = 0; long totalSwapPss = 0; long totalMemtrack = 0; + long totalMemtrackGraphics = 0; + long totalMemtrackGl = 0; for (int i = 0, size = memInfos.size(); i < size; i++) { ProcessMemInfo mi = memInfos.get(i); if (mi.pss == 0) { @@ -1355,6 +1357,8 @@ public class AppProfiler { totalPss += mi.pss; totalSwapPss += mi.swapPss; totalMemtrack += mi.memtrack; + totalMemtrackGraphics += memtrackTmp[1]; + totalMemtrackGl += memtrackTmp[2]; } Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { @@ -1521,10 +1525,24 @@ public class AppProfiler { } else { final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); if (totalExportedDmabuf >= 0) { + final long dmabufMapped = Debug.getDmabufMappedSizeKb(); + final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped; memInfoBuilder.append("DMA-BUF: "); memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf)); memInfoBuilder.append("\n"); - kernelUsed += totalExportedDmabuf; + // Account unmapped dmabufs as part of kernel memory allocations + kernelUsed += dmabufUnmapped; + // Replace memtrack HAL reported Graphics category with mapped dmabufs + totalPss -= totalMemtrackGraphics; + totalPss += dmabufMapped; + } + // These are included in the totalExportedDmabuf above and hence do not need to be added + // to kernelUsed. + final long totalExportedDmabufHeap = Debug.getDmabufHeapTotalExportedKb(); + if (totalExportedDmabufHeap >= 0) { + memInfoBuilder.append("DMA-BUF Heap: "); + memInfoBuilder.append(stringifyKBSize(totalExportedDmabufHeap)); + memInfoBuilder.append("\n"); } final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); @@ -1537,19 +1555,34 @@ public class AppProfiler { final long gpuUsage = Debug.getGpuTotalUsageKb(); if (gpuUsage >= 0) { - memInfoBuilder.append(" GPU: "); - memInfoBuilder.append(stringifyKBSize(gpuUsage)); - memInfoBuilder.append("\n"); + final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb(); + if (gpuDmaBufUsage >= 0) { + final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage; + memInfoBuilder.append(" GPU: "); + memInfoBuilder.append(stringifyKBSize(gpuUsage)); + memInfoBuilder.append(" ("); + memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage)); + memInfoBuilder.append(" dmabuf + "); + memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage)); + memInfoBuilder.append(" private)\n"); + // Replace memtrack HAL reported GL category with private GPU allocations and + // account it as part of kernel memory allocations + totalPss -= totalMemtrackGl; + kernelUsed += gpuPrivateUsage; + } else { + memInfoBuilder.append(" GPU: "); + memInfoBuilder.append(stringifyKBSize(gpuUsage)); + memInfoBuilder.append("\n"); + } + } memInfoBuilder.append(" Used RAM: "); memInfoBuilder.append(stringifyKBSize( totalPss - cachedPss + kernelUsed)); memInfoBuilder.append("\n"); - /* - * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of - * memInfo.getCachedSizeKb(). - */ + // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of + // memInfo.getCachedSizeKb(). memInfoBuilder.append(" Lost RAM: "); memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index b9943897a486..52bb55f12d79 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -149,7 +149,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s, * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER} */ - // TODO: Hook this up (it isn't used yet) + // TODO(b/180029015): Hook this up (it isn't used yet) @GuardedBy("mWorkerLock") private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null; @@ -818,14 +818,14 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { if (energyConsumerIds.isEmpty()) { return null; } - // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray() + // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray return getEnergyConsumptionData(); } @GuardedBy("mWorkerLock") private void addEnergyConsumerIdLocked( List<Integer> energyConsumerIds, @EnergyConsumerType int type) { - final int consumerId = 0; // TODO: Use mEnergyConsumerTypeToIdMap to get this + final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this energyConsumerIds.add(consumerId); } @@ -840,7 +840,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { return null; } - // TODO: Initialize typeToIds + // TODO(b/180029015): Initialize typeToIds // Maps type -> {ids} (1:n map, since multiple ids might have the same type) // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>(); @@ -862,9 +862,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } } idToConsumer.put(consumer.id, consumer); - // TODO: Also populate typeToIds map + // TODO(b/180029015): Also populate typeToIds map } - // TODO: Store typeToIds in mEnergyConsumerTypeToIdMap. + // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap. return idToConsumer; } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 0576345686fc..172f7073852a 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2407,6 +2407,11 @@ public final class ProcessList { app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap, false, false, new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()}); + + if (Process.createProcessGroup(uid, startResult.pid) < 0) { + Slog.e(ActivityManagerService.TAG, "Unable to create process group for " + + app.processName + " (" + startResult.pid + ")"); + } } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index e533cc3512d3..47573f31440b 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -607,7 +607,8 @@ final class ProcessProfileRecord { @GuardedBy("mService") void dumpPss(PrintWriter pw, String prefix, long nowUptime) { synchronized (mProfilerLock) { - pw.print(" lastPssTime="); + pw.print(prefix); + pw.print("lastPssTime="); TimeUtils.formatDuration(mLastPssTime, nowUptime, pw); pw.print(" pssProcState="); pw.print(mPssProcState); @@ -629,9 +630,8 @@ final class ProcessProfileRecord { DebugUtils.printSizeValue(pw, mLastRss * 1024); pw.println(); pw.print(prefix); - pw.print(" trimMemoryLevel="); + pw.print("trimMemoryLevel="); pw.println(mTrimMemoryLevel); - pw.println(); pw.print(prefix); pw.print("procStateMemTracker: "); mProcStateMemTracker.dumpLine(pw); pw.print(prefix); @@ -653,5 +653,6 @@ final class ProcessProfileRecord { pw.print(" timeUsed="); TimeUtils.formatDuration(mCurCpuTime.get() - lastCpuTime, pw); } + pw.println(); } } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index da8aeb52f3c2..42e7ff4c5724 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -425,9 +425,10 @@ class ProcessRecord implements WindowProcessListener { pw.print(prefix); pw.print("mInstr="); pw.println(mInstr); } pw.print(prefix); pw.print("thread="); pw.println(mThread); - pw.print(prefix); pw.print("pid="); pw.print(mPid); + pw.print(prefix); pw.print("pid="); pw.println(mPid); pw.print(prefix); pw.print("lastActivityTime="); TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw); + pw.println(); if (mPersistent || mRemoved) { pw.print(prefix); pw.print("persistent="); pw.print(mPersistent); pw.print(" removed="); pw.println(mRemoved); diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index e1a153dfa920..499fbcb0642d 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -1277,7 +1277,7 @@ final class ProcessStateRecord { pw.print(" set="); pw.println(mSetAdj); pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup); pw.print(" setSchedGroup="); pw.print(mSetSchedGroup); - pw.print(" systemNoUi="); pw.print(mSystemNoUi); + pw.print(" systemNoUi="); pw.println(mSystemNoUi); pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState()); pw.print(" mRepProcState="); pw.print(mRepProcState); pw.print(" setProcState="); pw.print(mSetProcState); @@ -1297,7 +1297,7 @@ final class ProcessStateRecord { } if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) { pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi); - pw.print(" pendingUiClean="); pw.print(mApp.mProfile.hasPendingUiClean()); + pw.print(" pendingUiClean="); pw.println(mApp.mProfile.hasPendingUiClean()); } pw.print(prefix); pw.print("cached="); pw.print(mCached); pw.print(" empty="); pw.println(mEmpty); @@ -1316,12 +1316,12 @@ final class ProcessStateRecord { } if (mHasForegroundActivities || mRepForegroundActivities) { pw.print(prefix); - pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities); + pw.print("foregroundActivities="); pw.print(mHasForegroundActivities); pw.print(" (rep="); pw.print(mRepForegroundActivities); pw.println(")"); } if (mSetProcState > ActivityManager.PROCESS_STATE_SERVICE) { pw.print(prefix); - pw.print(" whenUnimportant="); + pw.print("whenUnimportant="); TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw); pw.println(); } diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index c9560437799e..f88820083768 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -257,11 +257,15 @@ public final class AuthSession implements IBinder.DeathRecipient { mUserId, mOpPackageName, mOperationId); + mState = STATE_AUTH_STARTED; } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } + } else { + // The UI was already showing :) + mState = STATE_AUTH_STARTED_UI_SHOWING; } - mState = STATE_AUTH_STARTED; + } } @@ -794,7 +798,7 @@ public final class AuthSession implements IBinder.DeathRecipient { case BiometricAuthenticator.TYPE_FINGERPRINT: return FingerprintManager.getAcquiredString(mContext, acquiredInfo, vendorCode); case BiometricAuthenticator.TYPE_FACE: - return FaceManager.getAcquiredString(mContext, acquiredInfo, vendorCode); + return FaceManager.getAuthHelpMessage(mContext, acquiredInfo, vendorCode); default: return null; } 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 81e90df5802b..4925ce0bb2b1 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 @@ -435,13 +435,7 @@ public class Sensor { mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */); mLockoutCache = new LockoutCache(); mAuthenticatorIds = new HashMap<>(); - mLazySession = () -> { - if (mTestHalEnabled) { - return new TestSession(mCurrentSession.mHalSessionCallback); - } else { - return mCurrentSession != null ? mCurrentSession.mSession : null; - } - }; + mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null; } @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() { @@ -497,6 +491,10 @@ public class Sensor { void setTestHalEnabled(boolean enabled) { Slog.w(mTag, "setTestHalEnabled: " + enabled); + if (enabled != mTestHalEnabled) { + // The framework should retrieve a new session from the HAL. + mCurrentSession = null; + } mTestHalEnabled = enabled; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java index a38da3ad70b3..ff65c931dd78 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java @@ -17,12 +17,14 @@ package com.android.server.biometrics.sensors.face.aidl; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.face.Error; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.ISessionCallback; import android.hardware.biometrics.face.SensorProps; import android.hardware.common.NativeHandle; import android.hardware.keymaster.HardwareAuthToken; +import android.os.RemoteException; import android.util.Slog; /** @@ -38,70 +40,96 @@ public class TestHal extends IFace.Stub { @Override public ISession createSession(int sensorId, int userId, ISessionCallback cb) { + Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId); + return new ISession.Stub() { @Override - public void generateChallenge(int cookie, int timeoutSec) { + public void generateChallenge(int cookie, int timeoutSec) throws RemoteException { Slog.w(TAG, "generateChallenge, cookie: " + cookie); + cb.onChallengeGenerated(0L); } @Override - public void revokeChallenge(int cookie, long challenge) { + public void revokeChallenge(int cookie, long challenge) throws RemoteException { Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie); + cb.onChallengeRevoked(challenge); } @Override public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, byte enrollmentType, byte[] features, NativeHandle previewSurface) { Slog.w(TAG, "enroll, cookie: " + cookie); - return null; + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal authenticate(int cookie, long operationId) { Slog.w(TAG, "authenticate, cookie: " + cookie); - return null; + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal detectInteraction(int cookie) { Slog.w(TAG, "detectInteraction, cookie: " + cookie); - return null; + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override - public void enumerateEnrollments(int cookie) { + public void enumerateEnrollments(int cookie) throws RemoteException { Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie); + cb.onEnrollmentsEnumerated(new int[0]); } @Override - public void removeEnrollments(int cookie, int[] enrollmentIds) { + public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException { Slog.w(TAG, "removeEnrollments, cookie: " + cookie); + cb.onEnrollmentsRemoved(enrollmentIds); } @Override - public void getFeatures(int cookie, int enrollmentId) { + public void getFeatures(int cookie, int enrollmentId) throws RemoteException { Slog.w(TAG, "getFeatures, cookie: " + cookie); + cb.onFeaturesRetrieved(new byte[0], enrollmentId); } @Override public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, - byte feature, boolean enabled) { + byte feature, boolean enabled) throws RemoteException { Slog.w(TAG, "setFeature, cookie: " + cookie); + cb.onFeatureSet(enrollmentId, feature); } @Override - public void getAuthenticatorId(int cookie) { + public void getAuthenticatorId(int cookie) throws RemoteException { Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie); + cb.onAuthenticatorIdRetrieved(0L); } @Override - public void invalidateAuthenticatorId(int cookie) { + public void invalidateAuthenticatorId(int cookie) throws RemoteException { Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie); + cb.onAuthenticatorIdInvalidated(0L); } @Override - public void resetLockout(int cookie, HardwareAuthToken hat) { + public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException { Slog.w(TAG, "resetLockout, cookie: " + cookie); + cb.onLockoutCleared(); } }; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java deleted file mode 100644 index 23e69885841a..000000000000 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java +++ /dev/null @@ -1,117 +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.biometrics.sensors.face.aidl; - -import android.annotation.NonNull; -import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.face.Error; -import android.hardware.biometrics.face.ISession; -import android.hardware.common.NativeHandle; -import android.hardware.keymaster.HardwareAuthToken; -import android.os.Binder; -import android.os.IBinder; -import android.util.Slog; - -/** - * Test session that provides mostly no-ops. - */ -public class TestSession extends ISession.Stub { - private static final String TAG = "FaceTestSession"; - - @NonNull - private final Sensor.HalSessionCallback mHalSessionCallback; - - TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) { - mHalSessionCallback = halSessionCallback; - } - - @Override - public void generateChallenge(int cookie, int timeoutSec) { - mHalSessionCallback.onChallengeGenerated(0 /* challenge */); - } - - @Override - public void revokeChallenge(int cookie, long challenge) { - mHalSessionCallback.onChallengeRevoked(challenge); - } - - @Override - public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, byte enrollmentType, - byte[] features, NativeHandle previewSurface) { - return null; - } - - @Override - public ICancellationSignal authenticate(int cookie, long operationId) { - return new ICancellationSignal() { - @Override - public void cancel() { - mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */); - } - - @Override - public IBinder asBinder() { - return new Binder(); - } - }; - } - - @Override - public ICancellationSignal detectInteraction(int cookie) { - return null; - } - - @Override - public void enumerateEnrollments(int cookie) { - - } - - @Override - public void removeEnrollments(int cookie, int[] enrollmentIds) { - - } - - @Override - public void getFeatures(int cookie, int enrollmentId) { - - } - - @Override - public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, byte feature, - boolean enabled) { - - } - - @Override - public void getAuthenticatorId(int cookie) { - Slog.d(TAG, "getAuthenticatorId"); - // Immediately return a value so the framework can continue with subsequent requests. - mHalSessionCallback.onAuthenticatorIdRetrieved(0); - } - - @Override - public void invalidateAuthenticatorId(int cookie) { - Slog.d(TAG, "invalidateAuthenticatorId"); - // Immediately return a value so the framework can continue with subsequent requests. - mHalSessionCallback.onAuthenticatorIdInvalidated(0); - } - - @Override - public void resetLockout(int cookie, HardwareAuthToken hat) { - mHalSessionCallback.onLockoutCleared(); - } -} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java index 00ca8025564d..13bd1c27d8c8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java @@ -98,8 +98,11 @@ public class TestHal extends IBiometricsFace.Stub { } @Override - public int enumerate() { + public int enumerate() throws RemoteException { Slog.w(TAG, "enumerate"); + if (mCallback != null) { + mCallback.onEnumerate(0 /* deviceId */, new ArrayList<>(), 0 /* userId */); + } return 0; } 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 73b59cfdf248..c83c0fba0133 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 @@ -415,13 +415,7 @@ class Sensor { mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher); mLockoutCache = new LockoutCache(); mAuthenticatorIds = new HashMap<>(); - mLazySession = () -> { - if (mTestHalEnabled) { - return new TestSession(mCurrentSession.mHalSessionCallback); - } else { - return mCurrentSession != null ? mCurrentSession.mSession : null; - } - }; + mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null; } @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() { @@ -476,7 +470,11 @@ class Sensor { } void setTestHalEnabled(boolean enabled) { - Slog.w(mTag, "setTestHalEnabled, enabled"); + Slog.w(mTag, "setTestHalEnabled: " + enabled); + if (enabled != mTestHalEnabled) { + // The framework should retrieve a new session from the HAL. + mCurrentSession = null; + } mTestHalEnabled = enabled; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java index 66b68eeb335b..8ed24b6f9d48 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java @@ -17,11 +17,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.fingerprint.Error; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.ISessionCallback; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.keymaster.HardwareAuthToken; +import android.os.RemoteException; import android.util.Slog; /** @@ -38,58 +40,82 @@ public class TestHal extends IFingerprint.Stub { @Override public ISession createSession(int sensorId, int userId, ISessionCallback cb) { + Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId); + return new ISession.Stub() { @Override - public void generateChallenge(int cookie, int timeoutSec) { + public void generateChallenge(int cookie, int timeoutSec) throws RemoteException { Slog.w(TAG, "generateChallenge, cookie: " + cookie); + cb.onChallengeGenerated(0L); } @Override - public void revokeChallenge(int cookie, long challenge) { + public void revokeChallenge(int cookie, long challenge) throws RemoteException { Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie); + cb.onChallengeRevoked(challenge); } @Override public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) { Slog.w(TAG, "enroll, cookie: " + cookie); - return null; + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal authenticate(int cookie, long operationId) { Slog.w(TAG, "authenticate, cookie: " + cookie); - return null; + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal detectInteraction(int cookie) { Slog.w(TAG, "detectInteraction, cookie: " + cookie); - return null; + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override - public void enumerateEnrollments(int cookie) { + public void enumerateEnrollments(int cookie) throws RemoteException { Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie); + cb.onEnrollmentsEnumerated(new int[0]); } @Override - public void removeEnrollments(int cookie, int[] enrollmentIds) { + public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException { Slog.w(TAG, "removeEnrollments, cookie: " + cookie); + cb.onEnrollmentsRemoved(enrollmentIds); } @Override - public void getAuthenticatorId(int cookie) { + public void getAuthenticatorId(int cookie) throws RemoteException { Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie); + cb.onAuthenticatorIdRetrieved(0L); } @Override - public void invalidateAuthenticatorId(int cookie) { + public void invalidateAuthenticatorId(int cookie) throws RemoteException { Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie); + cb.onAuthenticatorIdInvalidated(0L); } @Override - public void resetLockout(int cookie, HardwareAuthToken hat) { + public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException { Slog.w(TAG, "resetLockout, cookie: " + cookie); + cb.onLockoutCleared(); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java deleted file mode 100644 index ac4f6651613d..000000000000 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java +++ /dev/null @@ -1,119 +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.biometrics.sensors.fingerprint.aidl; - -import android.annotation.NonNull; -import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.face.Error; -import android.hardware.biometrics.fingerprint.ISession; -import android.hardware.keymaster.HardwareAuthToken; -import android.os.Binder; -import android.os.IBinder; -import android.util.Slog; - -/** - * Test session that provides mostly no-ops. - */ -class TestSession extends ISession.Stub { - - private static final String TAG = "FingerprintTestSession"; - - @NonNull private final Sensor.HalSessionCallback mHalSessionCallback; - - TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) { - mHalSessionCallback = halSessionCallback; - } - - @Override - public void generateChallenge(int cookie, int timeoutSec) { - mHalSessionCallback.onChallengeGenerated(0 /* challenge */); - } - - @Override - public void revokeChallenge(int cookie, long challenge) { - mHalSessionCallback.onChallengeRevoked(challenge); - } - - @Override - public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) { - return null; - } - - @Override - public ICancellationSignal authenticate(int cookie, long operationId) { - return new ICancellationSignal() { - @Override - public void cancel() { - mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */); - } - - @Override - public IBinder asBinder() { - return new Binder(); - } - }; - } - - @Override - public ICancellationSignal detectInteraction(int cookie) { - return null; - } - - @Override - public void enumerateEnrollments(int cookie) { - Slog.d(TAG, "enumerate"); - } - - @Override - public void removeEnrollments(int cookie, int[] enrollmentIds) { - Slog.d(TAG, "remove"); - } - - @Override - public void getAuthenticatorId(int cookie) { - Slog.d(TAG, "getAuthenticatorId"); - // Immediately return a value so the framework can continue with subsequent requests. - mHalSessionCallback.onAuthenticatorIdRetrieved(0); - } - - @Override - public void invalidateAuthenticatorId(int cookie) { - Slog.d(TAG, "invalidateAuthenticatorId"); - // Immediately return a value so the framework can continue with subsequent requests. - mHalSessionCallback.onAuthenticatorIdInvalidated(0); - } - - @Override - public void resetLockout(int cookie, HardwareAuthToken hat) { - mHalSessionCallback.onLockoutCleared(); - } - - @Override - public void onPointerDown(int pointerId, int x, int y, float minor, float major) { - - } - - @Override - public void onPointerUp(int pointerId) { - - } - - @Override - public void onUiReady() { - - } -} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java index 57447f3a8cf7..14fdb507b0b1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java @@ -83,8 +83,12 @@ public class TestHal extends IBiometricsFingerprint.Stub { } @Override - public int enumerate() { + public int enumerate() throws RemoteException { Slog.w(TAG, "Enumerate"); + if (mCallback != null) { + mCallback.onEnumerate(0 /* deviceId */, 0 /* fingerId */, 0 /* groupId */, + 0 /* remaining */); + } return 0; } diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index e3757dfc6a59..df83df9a73fb 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -16,15 +16,24 @@ package com.android.server.compat; +import static android.app.compat.PackageOverride.VALUE_DISABLED; +import static android.app.compat.PackageOverride.VALUE_ENABLED; +import static android.app.compat.PackageOverride.VALUE_UNDEFINED; + import android.annotation.Nullable; +import android.app.compat.PackageOverride; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; +import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; import com.android.server.compat.overrides.ChangeOverrides; import com.android.server.compat.overrides.OverrideValue; +import com.android.server.compat.overrides.RawOverrideValue; import java.util.HashMap; import java.util.List; @@ -36,7 +45,7 @@ import java.util.Map; * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk} * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any * target SDK criteria set. These settings can be overridden for a specific package using - * {@link #addPackageOverride(String, boolean)}. + * {@link #addPackageOverrideInternal(String, boolean)}. * * <p>Note, this class is not thread safe so callers must ensure thread safety. */ @@ -63,8 +72,8 @@ public final class CompatChange extends CompatibilityChangeInfo { ChangeListener mListener = null; - private Map<String, Boolean> mPackageOverrides; - private Map<String, Boolean> mDeferredOverrides; + private Map<String, Boolean> mEvaluatedOverrides; + private Map<String, PackageOverride> mRawOverrides; public CompatChange(long changeId) { this(changeId, null, -1, -1, false, false, null, false); @@ -113,18 +122,26 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param pname Package name to enable the change for. * @param enabled Whether or not to enable the change. */ - void addPackageOverride(String pname, boolean enabled) { + private void addPackageOverrideInternal(String pname, boolean enabled) { if (getLoggingOnly()) { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mPackageOverrides == null) { - mPackageOverrides = new HashMap<>(); + if (mEvaluatedOverrides == null) { + mEvaluatedOverrides = new HashMap<>(); } - mPackageOverrides.put(pname, enabled); + mEvaluatedOverrides.put(pname, enabled); notifyListener(pname); } + private void removePackageOverrideInternal(String pname) { + if (mEvaluatedOverrides != null) { + if (mEvaluatedOverrides.remove(pname) != null) { + notifyListener(pname); + } + } + } + /** * Tentatively set the state of this change for a given package name. * The override will only take effect after that package is installed, if applicable. @@ -132,17 +149,19 @@ public final class CompatChange extends CompatibilityChangeInfo { * <p>Note, this method is not thread safe so callers must ensure thread safety. * * @param packageName Package name to tentatively enable the change for. - * @param enabled Whether or not to enable the change. + * @param override The package override to be set */ - void addPackageDeferredOverride(String packageName, boolean enabled) { + void addPackageOverride(String packageName, PackageOverride override, + OverrideAllowedState allowedState, Context context) { if (getLoggingOnly()) { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mDeferredOverrides == null) { - mDeferredOverrides = new HashMap<>(); + if (mRawOverrides == null) { + mRawOverrides = new HashMap<>(); } - mDeferredOverrides.put(packageName, enabled); + mRawOverrides.put(packageName, override); + recheckOverride(packageName, allowedState, context); } /** @@ -157,24 +176,44 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the recheck yielded a result that requires invalidating caches * (a deferred override was consolidated or a regular override was removed). */ - boolean recheckOverride(String packageName, boolean allowed) { - // A deferred override now is allowed by the policy, so promote it to a regular override. - if (hasDeferredOverride(packageName) && allowed) { - boolean overrideValue = mDeferredOverrides.remove(packageName); - addPackageOverride(packageName, overrideValue); - return true; + boolean recheckOverride(String packageName, OverrideAllowedState allowedState, + Context context) { + boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED); + + Long version = null; + try { + ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo( + packageName, 0); + version = applicationInfo.longVersionCode; + } catch (PackageManager.NameNotFoundException e) { + // Do nothing } - // A previously set override is no longer allowed by the policy, so make it deferred. - if (hasOverride(packageName) && !allowed) { - boolean overrideValue = mPackageOverrides.remove(packageName); - addPackageDeferredOverride(packageName, overrideValue); - // Notify because the override was removed. - notifyListener(packageName); - return true; + + // If the app is not installed or no longer has raw overrides, evaluate to false + if (version == null || !hasRawOverride(packageName) || !allowed) { + removePackageOverrideInternal(packageName); + return false; } - return false; + + // Evaluate the override based on its version + int overrideValue = mRawOverrides.get(packageName).evaluate(version); + switch (overrideValue) { + case VALUE_UNDEFINED: + removePackageOverrideInternal(packageName); + break; + case VALUE_ENABLED: + addPackageOverrideInternal(packageName, true); + break; + case VALUE_DISABLED: + addPackageOverrideInternal(packageName, false); + break; + } + return true; } + boolean hasPackageOverride(String pname) { + return mRawOverrides != null && mRawOverrides.containsKey(pname); + } /** * Remove any package override for the given package name, restoring the default behaviour. * @@ -182,15 +221,13 @@ public final class CompatChange extends CompatibilityChangeInfo { * * @param pname Package name to reset to defaults for. */ - void removePackageOverride(String pname) { - if (mPackageOverrides != null) { - if (mPackageOverrides.remove(pname) != null) { - notifyListener(pname); - } - } - if (mDeferredOverrides != null) { - mDeferredOverrides.remove(pname); + boolean removePackageOverride(String pname, OverrideAllowedState allowedState, + Context context) { + if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) { + recheckOverride(pname, allowedState, context); + return true; } + return false; } /** @@ -204,8 +241,8 @@ public final class CompatChange extends CompatibilityChangeInfo { if (app == null) { return defaultValue(); } - if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) { - return mPackageOverrides.get(app.packageName); + if (mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(app.packageName)) { + return mEvaluatedOverrides.get(app.packageName); } if (getDisabled()) { return false; @@ -223,8 +260,16 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the change should be enabled for the package. */ boolean willBeEnabled(String packageName) { - if (hasDeferredOverride(packageName)) { - return mDeferredOverrides.get(packageName); + if (hasRawOverride(packageName)) { + int eval = mRawOverrides.get(packageName).evaluateForAllVersions(); + switch (eval) { + case VALUE_ENABLED: + return true; + case VALUE_DISABLED: + return false; + case VALUE_UNDEFINED: + return defaultValue(); + } } return defaultValue(); } @@ -243,8 +288,8 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param packageName name of the package * @return true if there is such override */ - boolean hasOverride(String packageName) { - return mPackageOverrides != null && mPackageOverrides.containsKey(packageName); + private boolean hasOverride(String packageName) { + return mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(packageName); } /** @@ -252,65 +297,77 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param packageName name of the package * @return true if there is such a deferred override */ - boolean hasDeferredOverride(String packageName) { - return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName); - } - - /** - * Checks whether a change has any package overrides. - * @return true if the change has at least one deferred override - */ - boolean hasAnyPackageOverride() { - return mDeferredOverrides != null && !mDeferredOverrides.isEmpty(); - } - - /** - * Checks whether a change has any deferred overrides. - * @return true if the change has at least one deferred override - */ - boolean hasAnyDeferredOverride() { - return mPackageOverrides != null && !mPackageOverrides.isEmpty(); + private boolean hasRawOverride(String packageName) { + return mRawOverrides != null && mRawOverrides.containsKey(packageName); } void loadOverrides(ChangeOverrides changeOverrides) { - if (mDeferredOverrides == null) { - mDeferredOverrides = new HashMap<>(); + if (mRawOverrides == null) { + mRawOverrides = new HashMap<>(); } - mDeferredOverrides.clear(); - for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { - mDeferredOverrides.put(override.getPackageName(), override.getEnabled()); + mRawOverrides.clear(); + + if (mEvaluatedOverrides == null) { + mEvaluatedOverrides = new HashMap<>(); } + mEvaluatedOverrides.clear(); - if (mPackageOverrides == null) { - mPackageOverrides = new HashMap<>(); + // Load deferred overrides for backwards compatibility + if (changeOverrides.getDeferred() != null) { + for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { + mRawOverrides.put(override.getPackageName(), + new PackageOverride.Builder().setEnabled( + override.getEnabled()).build()); + } + } + + // Load validated overrides. For backwards compatibility, we also add them to raw overrides. + if (changeOverrides.getValidated() != null) { + for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { + mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled()); + mRawOverrides.put(override.getPackageName(), + new PackageOverride.Builder().setEnabled( + override.getEnabled()).build()); + } } - mPackageOverrides.clear(); - for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { - mPackageOverrides.put(override.getPackageName(), override.getEnabled()); + + // Load raw overrides + if (changeOverrides.getRaw() != null) { + for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) { + PackageOverride packageOverride = new PackageOverride.Builder() + .setMinVersionCode(override.getMinVersionCode()) + .setMaxVersionCode(override.getMaxVersionCode()) + .setEnabled(override.getEnabled()) + .build(); + mRawOverrides.put(override.getPackageName(), packageOverride); + } } } ChangeOverrides saveOverrides() { - if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) { + if (mRawOverrides == null || mRawOverrides.isEmpty()) { return null; } ChangeOverrides changeOverrides = new ChangeOverrides(); changeOverrides.setChangeId(getId()); - ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred(); - List<OverrideValue> deferredList = deferredOverrides.getOverrideValue(); - if (mDeferredOverrides != null) { - for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) { - OverrideValue override = new OverrideValue(); + ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw(); + List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue(); + if (mRawOverrides != null) { + for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) { + RawOverrideValue override = new RawOverrideValue(); override.setPackageName(entry.getKey()); - override.setEnabled(entry.getValue()); - deferredList.add(override); + override.setMinVersionCode(entry.getValue().getMinVersionCode()); + override.setMaxVersionCode(entry.getValue().getMaxVersionCode()); + override.setEnabled(entry.getValue().getEnabled()); + rawList.add(override); } } - changeOverrides.setDeferred(deferredOverrides); + changeOverrides.setRaw(rawOverrides); + ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated(); List<OverrideValue> validatedList = validatedOverrides.getOverrideValue(); - if (mPackageOverrides != null) { - for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) { + if (mEvaluatedOverrides != null) { + for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) { OverrideValue override = new OverrideValue(); override.setPackageName(entry.getKey()); override.setEnabled(entry.getValue()); @@ -337,11 +394,11 @@ public final class CompatChange extends CompatibilityChangeInfo { if (getLoggingOnly()) { sb.append("; loggingOnly"); } - if (mPackageOverrides != null && mPackageOverrides.size() > 0) { - sb.append("; packageOverrides=").append(mPackageOverrides); + if (mEvaluatedOverrides != null && mEvaluatedOverrides.size() > 0) { + sb.append("; packageOverrides=").append(mEvaluatedOverrides); } - if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) { - sb.append("; deferredOverrides=").append(mDeferredOverrides); + if (mRawOverrides != null && mRawOverrides.size() > 0) { + sb.append("; rawOverrides=").append(mRawOverrides); } if (getOverridable()) { sb.append("; overridable"); diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 6b77b9d4ce39..422991e082a9 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -17,6 +17,7 @@ package com.android.server.compat; import android.app.compat.ChangeIdStateCache; +import android.app.compat.PackageOverride; import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -31,6 +32,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; @@ -70,11 +72,13 @@ final class CompatConfig { private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); private final OverrideValidatorImpl mOverrideValidator; + private Context mContext; private File mOverridesFile; @VisibleForTesting CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this); + mContext = context; } static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) { @@ -210,17 +214,33 @@ final class CompatConfig { * @throws IllegalStateException if overriding is not allowed */ boolean addOverride(long changeId, String packageName, boolean enabled) { - boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled); + boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(enabled).build()); saveOverrides(); invalidateCache(); return alreadyKnown; } /** - * Unsafe version of {@link #addOverride(long, String, boolean)}. - * It does not invalidate the cache nor save the overrides. + * Overrides the enabled state for a given change and app. + * + * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. + * + * @param overrides list of overrides to default changes config. + * @param packageName app for which the overrides will be applied. */ - private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) { + void addOverrides(CompatibilityOverrideConfig overrides, String packageName) { + synchronized (mChanges) { + for (Long changeId : overrides.overrides.keySet()) { + addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId)); + } + saveOverrides(); + invalidateCache(); + } + } + + private boolean addOverrideUnsafe(long changeId, String packageName, + PackageOverride overrides) { boolean alreadyKnown = true; OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); @@ -232,17 +252,8 @@ final class CompatConfig { c = new CompatChange(changeId); addChange(c); } - switch (allowedState.state) { - case OverrideAllowedState.ALLOWED: - c.addPackageOverride(packageName, enabled); - break; - case OverrideAllowedState.DEFERRED_VERIFICATION: - c.addPackageDeferredOverride(packageName, enabled); - break; - default: - throw new IllegalStateException("Should only be able to override changes that " - + "are allowed or can be deferred."); - } + c.addPackageOverride(packageName, overrides, allowedState, mContext); + invalidateCache(); } return alreadyKnown; } @@ -311,47 +322,20 @@ final class CompatConfig { * It does not invalidate the cache nor save the overrides. */ private boolean removeOverrideUnsafe(long changeId, String packageName) { - boolean overrideExists = false; synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c != null) { - // Always allow removing a deferred override. - if (c.hasDeferredOverride(packageName)) { - c.removePackageOverride(packageName); - overrideExists = true; - } else if (c.hasOverride(packageName)) { - // Regular overrides need to pass the policy. - overrideExists = true; - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(changeId, packageName); + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(changeId, packageName); + if (c.hasPackageOverride(packageName)) { allowedState.enforce(changeId, packageName); - c.removePackageOverride(packageName); + c.removePackageOverride(packageName, allowedState, mContext); + invalidateCache(); + return true; } } } - return overrideExists; - } - - /** - * Overrides the enabled state for a given change and app. - * - * <p>Note: package overrides are not persistent and will be lost on system or runtime restart. - * - * @param overrides list of overrides to default changes config - * @param packageName app for which the overrides will be applied - */ - void addOverrides(CompatibilityChangeConfig overrides, String packageName) { - synchronized (mChanges) { - for (Long changeId : overrides.enabledChanges()) { - addOverrideUnsafe(changeId, packageName, true); - } - for (Long changeId : overrides.disabledChanges()) { - addOverrideUnsafe(changeId, packageName, false); - - } - saveOverrides(); - invalidateCache(); - } + return false; } /** @@ -402,7 +386,8 @@ final class CompatConfig { int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, true); + addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(true).build()); } saveOverrides(); invalidateCache(); @@ -418,7 +403,8 @@ final class CompatConfig { int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, false); + addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(false).build()); } saveOverrides(); invalidateCache(); @@ -615,8 +601,7 @@ final class CompatConfig { CompatChange c = mChanges.valueAt(idx); OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(c.getId(), packageName); - boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED); - shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride); + shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, mContext); } if (shouldInvalidateCache) { invalidateCache(); diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 51ba5f775880..d17753fe81bd 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -25,6 +25,7 @@ import static android.os.Process.SYSTEM_UID; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; +import android.app.compat.PackageOverride; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -43,6 +44,7 @@ import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.ChangeReporter; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.IPlatformCompat; import com.android.internal.util.DumpUtils; @@ -51,6 +53,8 @@ import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** * System server internal API for gating and reporting compatibility changes. @@ -161,6 +165,22 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { checkCompatChangeOverridePermission(); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); + for (long change : overrides.enabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); + } + for (long change : overrides.disabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) + .build()); + } + mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); + killPackage(packageName); + } + + @Override + public void setOverridesFromInstaller(CompatibilityOverrideConfig overrides, + String packageName) { + checkCompatChangeOverridePermission(); mCompatConfig.addOverrides(overrides, packageName); killPackage(packageName); } @@ -168,7 +188,15 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { checkCompatChangeOverridePermission(); - mCompatConfig.addOverrides(overrides, packageName); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); + for (long change : overrides.enabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); + } + for (long change : overrides.disabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) + .build()); + } + mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); } @Override diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index 397af7ba2991..61b11a5851a9 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -16,7 +16,6 @@ package com.android.server.connectivity; -import static android.net.NetworkCapabilities.MAX_TRANSPORT; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -25,15 +24,14 @@ import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; -import android.net.ConnectivityManager; import android.net.ConnectivityMetricsEvent; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; +import android.net.metrics.ConnectStats; import android.net.metrics.DefaultNetworkEvent; import android.net.metrics.DhcpClientEvent; import android.net.metrics.DhcpErrorEvent; import android.net.metrics.DnsEvent; -import android.net.metrics.ConnectStats; import android.net.metrics.IpManagerEvent; import android.net.metrics.IpReachabilityEvent; import android.net.metrics.NetworkEvent; @@ -41,12 +39,13 @@ import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; import android.net.metrics.WakeupStats; import android.os.Parcelable; -import android.util.SparseArray; import android.util.SparseIntArray; + import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -361,29 +360,22 @@ final public class IpConnectivityEventBuilder { return IpConnectivityLogClass.UNKNOWN; case 1: int t = Long.numberOfTrailingZeros(transports); - return transportToLinkLayer(t); + return TRANSPORT_LINKLAYER_MAP.get(t, IpConnectivityLogClass.UNKNOWN); default: return IpConnectivityLogClass.MULTIPLE; } } - private static int transportToLinkLayer(int transport) { - if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) { - return TRANSPORT_LINKLAYER_MAP[transport]; - } - return IpConnectivityLogClass.UNKNOWN; - } - - private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1]; + private static final SparseIntArray TRANSPORT_LINKLAYER_MAP = new SparseIntArray(); static { - TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR] = IpConnectivityLogClass.CELLULAR; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI] = IpConnectivityLogClass.WIFI; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH] = IpConnectivityLogClass.BLUETOOTH; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET] = IpConnectivityLogClass.ETHERNET; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN] = IpConnectivityLogClass.UNKNOWN; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.WIFI_NAN; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_LOWPAN] = IpConnectivityLogClass.LOWPAN; - }; + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_CELLULAR, IpConnectivityLogClass.CELLULAR); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI, IpConnectivityLogClass.WIFI); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_BLUETOOTH, IpConnectivityLogClass.BLUETOOTH); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_ETHERNET, IpConnectivityLogClass.ETHERNET); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_VPN, IpConnectivityLogClass.UNKNOWN); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI_AWARE, IpConnectivityLogClass.WIFI_NAN); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_LOWPAN, IpConnectivityLogClass.LOWPAN); + } private static int ifnameToLinkLayer(String ifname) { // Do not try to catch all interface names with regexes, instead only catch patterns that diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 6f112d73ba14..508739f2e1e0 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -79,7 +79,6 @@ public class NetworkNotificationManager { // server. public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS"; public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS"; - public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; // The context is for the current user (system server) private final Context mContext; diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java index 87b4c162a2cc..7ef315c469ae 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -27,7 +27,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.telephony.data.EpsBearerQosSessionAttributes; -import android.util.Slog; +import android.util.Log; import com.android.internal.util.CollectionUtils; import com.android.server.ConnectivityService; @@ -260,18 +260,18 @@ public class QosCallbackTracker { } private static void log(final String msg) { - Slog.d(TAG, msg); + Log.d(TAG, msg); } private static void logw(final String msg) { - Slog.w(TAG, msg); + Log.w(TAG, msg); } private static void loge(final String msg) { - Slog.e(TAG, msg); + Log.e(TAG, msg); } private static void logwtf(final String msg) { - Slog.wtf(TAG, msg); + Log.wtf(TAG, msg); } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 33c19b1ca2b2..a769e88f77d7 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -21,10 +21,10 @@ import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.Manifest; import android.annotation.NonNull; @@ -172,6 +172,12 @@ public class Vpn { */ @VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB + /** + * Network score that VPNs will announce to ConnectivityService. + * TODO: remove when the network scoring refactor lands. + */ + private static final int VPN_DEFAULT_SCORE = 101; + // TODO: create separate trackers for each unique VPN to support // automated reconnection @@ -496,6 +502,11 @@ public class Vpn { updateAlwaysOnNotification(detailedState); } + private void resetNetworkCapabilities() { + mNetworkCapabilities.setUids(null); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); + } + /** * Chooses whether to force all connections to go though VPN. * @@ -520,6 +531,11 @@ public class Vpn { } } + /** Returns the package name that is currently prepared. */ + public String getPackage() { + return mPackage; + } + /** * Check whether to prevent all traffic outside of a VPN even when the VPN is not connected. * @@ -930,8 +946,7 @@ public class Vpn { agentDisconnect(); jniReset(mInterface); mInterface = null; - mNetworkCapabilities.setUids(null); - mNetworkCapabilities.setTransportInfo(null); + resetNetworkCapabilities(); } // Revoke the connection or stop the VpnRunner. @@ -1229,8 +1244,7 @@ public class Vpn { } mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */, - mNetworkCapabilities, lp, - ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) { + mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) { @Override public void unwanted() { // We are user controlled, not driven by NetworkRequest. @@ -1744,8 +1758,7 @@ public class Vpn { private void cleanupVpnStateLocked() { mStatusIntent = null; - mNetworkCapabilities.setUids(null); - mNetworkCapabilities.setTransportInfo(null); + resetNetworkCapabilities(); mConfig = null; mInterface = null; diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index e496d77deaf5..e693bcc93f8f 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -16,9 +16,14 @@ package com.android.server.devicestate; +import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; + import android.annotation.IntRange; import android.annotation.NonNull; +import com.android.internal.util.Preconditions; + import java.util.Objects; /** @@ -35,24 +40,25 @@ import java.util.Objects; */ public final class DeviceState { /** Unique identifier for the device state. */ - @IntRange(from = 0) + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) private final int mIdentifier; /** String description of the device state. */ @NonNull private final String mName; - public DeviceState(@IntRange(from = 0) int identifier, + public DeviceState( + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, @NonNull String name) { - if (identifier < 0) { - throw new IllegalArgumentException("Identifier must be greater than or equal to zero."); - } + Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE, + "identifier"); + mIdentifier = identifier; mName = name; } /** Returns the unique identifier for the device state. */ - @IntRange(from = 0) + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) public int getIdentifier() { return mIdentifier; } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 984a17694e07..b3a6f263953f 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -17,6 +17,8 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES; import android.annotation.IntRange; @@ -89,7 +91,7 @@ public final class DeviceStateManagerService extends SystemService { // the current state after the initial callback from the DeviceStateProvider. @GuardedBy("mLock") @NonNull - private DeviceState mCommittedState = new DeviceState(0, "UNSET"); + private DeviceState mCommittedState = new DeviceState(MINIMUM_DEVICE_STATE, "UNSET"); // The device state that is currently awaiting callback from the policy to be committed. @GuardedBy("mLock") @NonNull @@ -598,8 +600,9 @@ public final class DeviceStateManagerService extends SystemService { } @Override - public void onStateChanged(@IntRange(from = 0) int identifier) { - if (identifier < 0) { + public void onStateChanged( + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier) { + if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) { throw new IllegalArgumentException("Invalid identifier: " + identifier); } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java index 2d4377f820fd..109bf6358e8b 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -16,6 +16,9 @@ package com.android.server.devicestate; +import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; + import android.annotation.IntRange; /** @@ -65,8 +68,10 @@ public interface DeviceStateProvider { * * @param identifier the identifier of the new device state. * - * @throws IllegalArgumentException if the state is less than 0. + * @throws IllegalArgumentException if the state is less than {@link MINIMUM_DEVICE_STATE} + * or greater than {@link MAXIMUM_DEVICE_STATE}. */ - void onStateChanged(@IntRange(from = 0) int identifier); + void onStateChanged( + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier); } } diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java index cb47bb25f152..86a8e36d748d 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java @@ -103,10 +103,11 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { if (mIsCec20) { sendSetStreamPath(); } - if (!mIsCec20 || mTarget.getDevicePowerStatus() - == HdmiControlManager.POWER_STATUS_UNKNOWN) { + int targetPowerStatus = localDevice().mService.getHdmiCecNetwork() + .getCecDeviceInfo(getTargetAddress()).getDevicePowerStatus(); + if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) { queryDevicePowerStatus(); - } else if (mTarget.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON) { + } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { invokeCallbackAndFinish(HdmiControlManager.RESULT_SUCCESS); return true; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java index 69180643661f..f64efe7c26cc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java @@ -411,6 +411,12 @@ public class HdmiCecConfig { case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED: notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE); break; + case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED: + notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY); + break; + case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED: + notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP); + break; } } @@ -447,6 +453,8 @@ public class HdmiCecConfig { Global.HDMI_CEC_VERSION, Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, + Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, + Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, }; for (String setting: settings) { resolver.registerContentObserver(Global.getUriFor(setting), false, diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 382f0f9d6329..d8914b389191 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -654,7 +654,9 @@ abstract class HdmiCecLocalDevice { FOLLOWER_SAFETY_TIMEOUT); return true; } - return false; + + mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); + return true; } @ServiceThreadOnly @@ -666,9 +668,8 @@ abstract class HdmiCecLocalDevice { final long upTime = SystemClock.uptimeMillis(); injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0); mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; - return true; } - return false; + return true; } static void injectKeyEvent(long time, int action, int keycode, int repeat) { @@ -788,10 +789,7 @@ abstract class HdmiCecLocalDevice { } protected boolean handleRecordTvScreen(HdmiCecMessage message) { - // 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 false; } protected boolean handleTimerClearedStatus(HdmiCecMessage message) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index a3e18d161751..8d6bcadb3e2b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1079,7 +1079,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { message.getSource(), HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); } - return super.handleRecordTvScreen(message); + // 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; } int recorderAddress = message.getSource(); diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java index 909fcda26c39..66fc0d9c1760 100644 --- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java +++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.util.SparseIntArray; @@ -108,7 +109,10 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { private void resetPowerStatus(List<HdmiDeviceInfo> deviceInfos) { mPowerStatus.clear(); for (HdmiDeviceInfo info : deviceInfos) { - mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus()); + if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0 + || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) { + mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus()); + } } } @@ -117,19 +121,22 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false); resetPowerStatus(deviceInfos); for (HdmiDeviceInfo info : deviceInfos) { - final int logicalAddress = info.getLogicalAddress(); - sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), - logicalAddress), - new SendMessageCallback() { - @Override - public void onSendCompleted(int error) { - // If fails to send <Give Device Power Status>, - // update power status into UNKNOWN. - if (error != SendMessageResult.SUCCESS) { - updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true); + if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0 + || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) { + final int logicalAddress = info.getLogicalAddress(); + sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), + logicalAddress), + new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + // If fails to send <Give Device Power Status>, + // update power status into UNKNOWN. + if (error != SendMessageResult.SUCCESS) { + updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true); + } } - } - }); + }); + } } mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 00ae30e2aa0e..0754df0e6b9f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -178,6 +178,7 @@ 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; @@ -5229,15 +5230,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - boolean asProto = false; - for (int argIndex = 0; argIndex < args.length; argIndex++) { - if (args[argIndex].equals(PROTO_ARG)) { - asProto = true; - break; - } - } - - if (asProto) { + if (ArrayUtils.contains(args, PROTO_ARG)) { final ImeTracing imeTracing = ImeTracing.getInstance(); if (imeTracing.isEnabled()) { imeTracing.stopTrace(null, false /* writeToFile */); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index cb9793f088c1..685e9e6ad420 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -279,6 +279,7 @@ public class LockSettingsService extends ILockSettings.Stub { super.onBootPhase(phase); if (phase == PHASE_ACTIVITY_MANAGER_READY) { mLockSettingsService.migrateOldDataAfterSystemReady(); + mLockSettingsService.loadEscrowData(); } } @@ -824,13 +825,17 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.initWeaverService(); getAuthSecretHal(); mDeviceProvisionedObserver.onSystemReady(); - mRebootEscrowManager.loadRebootEscrowDataIfAvailable(); + // TODO: maybe skip this for split system user mode. mStorage.prefetchUser(UserHandle.USER_SYSTEM); mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(), mInjector.getFaceManager()); } + private void loadEscrowData() { + mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler); + } + private void getAuthSecretHal() { try { mAuthSecretService = IAuthSecret.getService(/* retry */ true); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 67fae05c64f4..6a5c2d89a4e2 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -187,6 +187,7 @@ class LockSettingsShellCommand extends ShellCommand { pw.println(""); pw.println(" remove-cache [--user USER_ID]"); pw.println(" Removes cached unified challenge for the managed profile."); + pw.println(""); pw.println(" set-resume-on-reboot-provider-package <package_name>"); pw.println(" Sets the package name for server based resume on reboot service " + "provider."); diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java index 38eeb88e63b0..af0774c6c3fa 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowData.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java @@ -35,6 +35,12 @@ class RebootEscrowData { */ private static final int CURRENT_VERSION = 2; + /** + * This is the legacy version of the escrow data format for R builds. The escrow data is only + * encrypted by the escrow key, without additional wrap of another key from keystore. + */ + private static final int LEGACY_SINGLE_ENCRYPTED_VERSION = 1; + private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob, RebootEscrowKey key) { mSpVersion = spVersion; @@ -64,6 +70,19 @@ class RebootEscrowData { return mKey; } + private static byte[] decryptBlobCurrentVersion(SecretKey kk, RebootEscrowKey ks, + DataInputStream dis) throws IOException { + if (kk == null) { + throw new IOException("Failed to find wrapper key in keystore, cannot decrypt the" + + " escrow data"); + } + + // Decrypt the blob with the key from keystore first, then decrypt again with the reboot + // escrow key. + byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis); + return AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob); + } + static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk) throws IOException { Objects.requireNonNull(ks); @@ -71,17 +90,20 @@ class RebootEscrowData { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob)); int version = dis.readInt(); - if (version != CURRENT_VERSION) { - throw new IOException("Unsupported version " + version); - } byte spVersion = dis.readByte(); - - // Decrypt the blob with the key from keystore first, then decrypt again with the reboot - // escrow key. - byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis); - final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob); - - return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + switch (version) { + case CURRENT_VERSION: { + byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis); + return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + } + case LEGACY_SINGLE_ENCRYPTED_VERSION: { + // Decrypt the blob with the escrow key directly. + byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), dis); + return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + } + default: + throw new IOException("Unsupported version " + version); + } } static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 06962d414009..30ea5556b41c 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -21,6 +21,7 @@ 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.UserManager; import android.provider.DeviceConfig; @@ -39,6 +40,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Objects; import javax.crypto.SecretKey; @@ -76,6 +78,13 @@ class RebootEscrowManager { private static final int BOOT_COUNT_TOLERANCE = 5; /** + * The default retry specs for loading reboot escrow data. We will attempt to retry loading + * escrow data on temporarily errors, e.g. unavailable network. + */ + private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3; + private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30; + + /** * Logs events for later debugging in bugreports. */ private final RebootEscrowEventLog mEventLog; @@ -137,6 +146,7 @@ class RebootEscrowManager { RebootEscrowProviderInterface rebootEscrowProvider; if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, "server_based_ror_enabled", false)) { + Slog.i(TAG, "Using server based resume on reboot"); rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); } else { rebootEscrowProvider = new RebootEscrowProviderHalImpl(); @@ -148,6 +158,14 @@ class RebootEscrowManager { return null; } + void post(Handler handler, Runnable runnable) { + handler.post(runnable); + } + + void postDelayed(Handler handler, Runnable runnable, long delayMillis) { + handler.postDelayed(runnable, delayMillis); + } + public Context getContext() { return mContext; } @@ -199,7 +217,18 @@ class RebootEscrowManager { mKeyStoreManager = injector.getKeyStoreManager(); } - void loadRebootEscrowDataIfAvailable() { + private void onGetRebootEscrowKeyFailed(List<UserInfo> users) { + Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); + for (UserInfo user : users) { + mStorage.removeRebootEscrow(user.id); + } + + // Clear the old key in keystore. + mKeyStoreManager.clearKeyStoreEncryptionKey(); + onEscrowRestoreComplete(false); + } + + void loadRebootEscrowDataIfAvailable(Handler retryHandler) { List<UserInfo> users = mUserManager.getUsers(); List<UserInfo> rebootEscrowUsers = new ArrayList<>(); for (UserInfo user : users) { @@ -212,17 +241,53 @@ class RebootEscrowManager { return; } + mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry( + retryHandler, 0, users, rebootEscrowUsers)); + } + + void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber, + List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { + Objects.requireNonNull(retryHandler); + + final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); + final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_interval_seconds", + DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS); + + if (attemptNumber < retryLimit) { + Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber); + mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry( + retryHandler, attemptNumber, users, rebootEscrowUsers), + retryIntervalInSeconds * 1000); + return; + } + + Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts"); + onGetRebootEscrowKeyFailed(users); + } + + void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber, + List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is // generated before reboot. Note that we will clear the escrow key even if the keystore key // is null. SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); - RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk); - if (kk == null || escrowKey == null) { - Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); - for (UserInfo user : users) { - mStorage.removeRebootEscrow(user.id); - } - onEscrowRestoreComplete(false); + if (kk == null) { + Slog.i(TAG, "Failed to load the key for resume on reboot from key store."); + } + + RebootEscrowKey escrowKey; + try { + escrowKey = getAndClearRebootEscrowKey(kk); + } catch (IOException e) { + scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users, + rebootEscrowUsers); + return; + } + + if (escrowKey == null) { + onGetRebootEscrowKeyFailed(users); return; } @@ -249,7 +314,7 @@ class RebootEscrowManager { } } - private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) { + private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException { RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider(); if (rebootEscrowProvider == null) { Slog.w(TAG, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java index 6c1040b596c8..4b00772088f2 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java @@ -33,7 +33,7 @@ import javax.crypto.SecretKey; * An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL. */ class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface { - private static final String TAG = "RebootEscrowProvider"; + private static final String TAG = "RebootEscrowProviderHal"; private final Injector mInjector; diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java index 857ad5fc312a..af6faad3c76e 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java @@ -16,6 +16,8 @@ package com.android.server.locksettings; +import java.io.IOException; + import javax.crypto.SecretKey; /** @@ -33,9 +35,10 @@ public interface RebootEscrowProviderInterface { /** * Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted, - * use the input key to decrypt the RebootEscrowKey. Returns null on failure. + * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an + * IOException if the failure is non-fatal, and a retry may succeed. */ - RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey); + RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException; /** * Clears the stored RebootEscrowKey. diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java index ba1a680ba7fb..b3b45460899d 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -35,7 +35,7 @@ import javax.crypto.SecretKey; * encrypt & decrypt the blob. */ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface { - private static final String TAG = "RebootEscrowProvider"; + private static final String TAG = "RebootEscrowProviderServerBased"; // Timeout for service binding private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10; @@ -50,6 +50,8 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa private final Injector mInjector; + private byte[] mServerBlob; + static class Injector { private ResumeOnRebootServiceConnection mServiceConnection = null; @@ -124,17 +126,25 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } @Override - public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) { - byte[] serverBlob = mStorage.readRebootEscrowServerBlob(); + public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException { + if (mServerBlob == null) { + mServerBlob = mStorage.readRebootEscrowServerBlob(); + } // Delete the server blob in storage. mStorage.removeRebootEscrowServerBlob(); - if (serverBlob == null) { + if (mServerBlob == null) { Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); return null; } + if (decryptionKey == null) { + Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is" + + " null."); + return null; + } + Slog.i(TAG, "Loaded reboot escrow server blob from storage"); try { - byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey); + byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey); if (escrowKeyBytes == null) { Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null"); return null; @@ -145,7 +155,7 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } return RebootEscrowKey.fromKeyBytes(escrowKeyBytes); - } catch (TimeoutException | RemoteException | IOException e) { + } catch (TimeoutException | RemoteException e) { Slog.w(TAG, "Failed to decrypt the server blob ", e); return null; } diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 6d1c68039ee5..3cc32bef0e67 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -16,10 +16,10 @@ package com.android.server.net; -import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static android.provider.Settings.ACTION_VPN_SETTINGS; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,43 +28,37 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; +import android.net.NetworkRequest; import android.os.Handler; import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; -import com.android.server.ConnectivityService; -import com.android.server.EventLogTags; import com.android.server.connectivity.Vpn; import java.util.List; import java.util.Objects; /** - * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be - * connected and kicks off VPN connection, managing any required {@code netd} - * firewall rules. + * State tracker for legacy lockdown VPN. Watches for physical networks to be + * connected and kicks off VPN connection. */ public class LockdownVpnTracker { private static final String TAG = "LockdownVpnTracker"; - /** Number of VPN attempts before waiting for user intervention. */ - private static final int MAX_ERROR_COUNT = 4; - public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; @NonNull private final Context mContext; - @NonNull private final ConnectivityService mConnService; + @NonNull private final ConnectivityManager mCm; @NonNull private final NotificationManager mNotificationManager; @NonNull private final Handler mHandler; @NonNull private final Vpn mVpn; @@ -76,19 +70,73 @@ public class LockdownVpnTracker { @NonNull private final PendingIntent mConfigIntent; @NonNull private final PendingIntent mResetIntent; + @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback(); + @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback(); + + private class NetworkCallback extends ConnectivityManager.NetworkCallback { + private Network mNetwork = null; + private LinkProperties mLinkProperties = null; + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties lp) { + boolean networkChanged = false; + if (!network.equals(mNetwork)) { + // The default network just changed. + mNetwork = network; + networkChanged = true; + } + mLinkProperties = lp; + // Backwards compatibility: previously, LockdownVpnTracker only responded to connects + // and disconnects, not LinkProperties changes on existing networks. + if (networkChanged) { + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + } + + @Override + public void onLost(Network network) { + // The default network has gone down. + mNetwork = null; + mLinkProperties = null; + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + + public Network getNetwork() { + return mNetwork; + } + + public LinkProperties getLinkProperties() { + return mLinkProperties; + } + } + + private class VpnNetworkCallback extends NetworkCallback { + @Override + public void onAvailable(Network network) { + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + @Override + public void onLost(Network network) { + onAvailable(network); + } + } + @Nullable private String mAcceptedEgressIface; - private int mErrorCount; - public LockdownVpnTracker(@NonNull Context context, - @NonNull ConnectivityService connService, @NonNull Handler handler, @NonNull KeyStore keyStore, @NonNull Vpn vpn, @NonNull VpnProfile profile) { mContext = Objects.requireNonNull(context); - mConnService = Objects.requireNonNull(connService); + mCm = mContext.getSystemService(ConnectivityManager.class); mHandler = Objects.requireNonNull(handler); mVpn = Objects.requireNonNull(vpn); mProfile = Objects.requireNonNull(profile); @@ -110,24 +158,20 @@ public class LockdownVpnTracker { * connection when ready, or setting firewall rules once VPN is connected. */ private void handleStateChangedLocked() { - - final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered(); - final LinkProperties egressProp = mConnService.getActiveLinkProperties(); + final Network network = mDefaultNetworkCallback.getNetwork(); + final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties(); final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig(); // Restart VPN when egress network disconnected or changed - final boolean egressDisconnected = egressInfo == null - || State.DISCONNECTED.equals(egressInfo.getState()); + final boolean egressDisconnected = (network == null); final boolean egressChanged = egressProp == null || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); - final int egressType = (egressInfo == null) ? TYPE_NONE : egressInfo.getType(); final String egressIface = (egressProp == null) ? null : egressProp.getInterfaceName(); - Log.d(TAG, "handleStateChanged: egress=" + egressType - + " " + mAcceptedEgressIface + "->" + egressIface); + Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface); if (egressDisconnected || egressChanged) { mAcceptedEgressIface = null; @@ -138,46 +182,49 @@ public class LockdownVpnTracker { return; } - if (vpnInfo.getDetailedState() == DetailedState.FAILED) { - EventLogTags.writeLockdownVpnError(egressType); - } - - if (mErrorCount > MAX_ERROR_COUNT) { - showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); - - } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) { - if (mProfile.isValidLockdownProfile()) { - Log.d(TAG, "Active network connected; starting VPN"); - EventLogTags.writeLockdownVpnConnecting(egressType); - showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); - - mAcceptedEgressIface = egressProp.getInterfaceName(); - try { - // Use the privileged method because Lockdown VPN is initiated by the system, so - // no additional permission checks are necessary. - mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp); - } catch (IllegalStateException e) { - mAcceptedEgressIface = null; - Log.e(TAG, "Failed to start VPN", e); - showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); - } - } else { + // At this point, |network| is known to be non-null. + if (!vpnInfo.isConnectedOrConnecting()) { + if (!mProfile.isValidLockdownProfile()) { Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + return; } + Log.d(TAG, "Active network connected; starting VPN"); + showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); + + mAcceptedEgressIface = egressIface; + try { + // Use the privileged method because Lockdown VPN is initiated by the system, so + // no additional permission checks are necessary. + // + // Pass in the underlying network here because the legacy VPN is, in fact, tightly + // coupled to a given underlying network and cannot provide mobility. This makes + // things marginally more correct in two ways: + // + // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra + // CONNECTED broadcast for the underlying network type. The underlying type comes + // from here. LTT *could* assume that the underlying network is the default + // network, but that might introduce a race condition if, say, the VPN starts + // connecting on cell, but when the connection succeeds and the agent is + // registered, the default network is now wifi. + // 2. If no underlying network is passed in, then CS will assume the underlying + // network is the system default. So, if the VPN is up and underlying network + // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have + // changed to match the new default network (e.g., cell). + mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp); + } catch (IllegalStateException e) { + mAcceptedEgressIface = null; + Log.e(TAG, "Failed to start VPN", e); + showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + } } else if (vpnInfo.isConnected() && vpnConfig != null) { final String iface = vpnConfig.interfaze; final List<LinkAddress> sourceAddrs = vpnConfig.addresses; Log.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddrs.toString()); - EventLogTags.writeLockdownVpnConnected(egressType); showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); - - final NetworkInfo clone = new NetworkInfo(egressInfo); - augmentNetworkInfo(clone); - mConnService.sendConnectedBroadcast(clone); } } @@ -192,7 +239,15 @@ public class LockdownVpnTracker { mVpn.setEnableTeardown(false); mVpn.setLockdown(true); + mCm.setLegacyLockdownVpnEnabled(true); handleStateChangedLocked(); + + mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); + final NetworkRequest vpnRequest = new NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(TRANSPORT_VPN) + .build(); + mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler); } public void shutdown() { @@ -205,20 +260,21 @@ public class LockdownVpnTracker { Log.d(TAG, "shutdownLocked()"); mAcceptedEgressIface = null; - mErrorCount = 0; mVpn.stopVpnRunnerPrivileged(); mVpn.setLockdown(false); + mCm.setLegacyLockdownVpnEnabled(false); hideNotification(); mVpn.setEnableTeardown(true); + mCm.unregisterNetworkCallback(mDefaultNetworkCallback); + mCm.unregisterNetworkCallback(mVpnNetworkCallback); } /** * Reset VPN lockdown tracker. Called by ConnectivityService when receiving * {@link #ACTION_LOCKDOWN_RESET} pending intent. */ - @GuardedBy("mConnService.mVpns") public void reset() { Log.d(TAG, "reset()"); synchronized (mStateLock) { @@ -229,28 +285,6 @@ public class LockdownVpnTracker { } } - public void onNetworkInfoChanged() { - synchronized (mStateLock) { - handleStateChangedLocked(); - } - } - - public void onVpnStateChanged(NetworkInfo info) { - if (info.getDetailedState() == DetailedState.FAILED) { - mErrorCount++; - } - synchronized (mStateLock) { - handleStateChangedLocked(); - } - } - - public void augmentNetworkInfo(NetworkInfo info) { - if (info.isConnected()) { - final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); - info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); - } - } - private void showNotification(int titleRes, int iconRes) { final Notification.Builder builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN) diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 769b781b1ae9..78c1a95a891d 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -21,6 +21,7 @@ import android.app.NotificationManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.IPackageManager; +import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.IBinder; import android.os.IInterface; @@ -197,6 +198,11 @@ public class ConditionProviders extends ManagedServices { } @Override + protected void ensureFilters(ServiceInfo si, int userId) { + // nothing to filter + } + + @Override protected void loadDefaultsFromConfig() { String defaultDndAccess = mContext.getResources().getString( R.string.config_defaultDndAccessPackages); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 21537e6aaa22..bbdcac28fb02 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -189,6 +189,8 @@ abstract public class ManagedServices { abstract protected void onServiceAdded(ManagedServiceInfo info); + abstract protected void ensureFilters(ServiceInfo si, int userId); + protected List<ManagedServiceInfo> getServices() { synchronized (mMutex) { List<ManagedServiceInfo> services = new ArrayList<>(mServices); @@ -1342,8 +1344,10 @@ abstract public class ManagedServices { for (ComponentName component : add) { try { ServiceInfo info = mPm.getServiceInfo(component, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + PackageManager.GET_META_DATA + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId); if (info == null) { Slog.w(TAG, "Not binding " + getCaption() + " service " + component + ": service not found"); @@ -1356,7 +1360,7 @@ abstract public class ManagedServices { } Slog.v(TAG, "enabling " + getCaption() + " for " + userId + ": " + component); - registerService(component, userId); + registerService(info, userId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -1368,9 +1372,15 @@ abstract public class ManagedServices { * Version of registerService that takes the name of a service component to bind to. */ @VisibleForTesting - void registerService(final ComponentName name, final int userid) { + void registerService(final ServiceInfo si, final int userId) { + ensureFilters(si, userId); + registerService(si.getComponentName(), userId); + } + + @VisibleForTesting + void registerService(final ComponentName cn, final int userId) { synchronized (mMutex) { - registerServiceLocked(name, userid); + registerServiceLocked(cn, userId); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5703ffec21a0..917be29243ad 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -177,6 +177,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; +import android.content.pm.VersionedPackage; import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioAttributes; @@ -214,6 +215,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.Condition; +import android.service.notification.ConditionProviderService; import android.service.notification.ConversationChannelWrapper; import android.service.notification.IConditionProvider; import android.service.notification.INotificationListener; @@ -8969,7 +8971,8 @@ public class NotificationManagerService extends SystemService { NotificationListenerFilter nls = mListeners.getNotificationListenerFilter(listener.mKey); if (nls != null && (!nls.isTypeAllowed(notificationType) - || !nls.isPackageAllowed(sbn.getPackageName()))) { + || !nls.isPackageAllowed( + new VersionedPackage(sbn.getPackageName(), sbn.getUid())))) { return false; } return true; @@ -9132,6 +9135,12 @@ public class NotificationManagerService extends SystemService { } @Override + protected void ensureFilters(ServiceInfo si, int userId) { + // nothing to filter; no user visible settings for types/packages like other + // listeners + } + + @Override @GuardedBy("mNotificationLock") protected void onServiceRemovedLocked(ManagedServiceInfo removed) { mListeners.unregisterService(removed.service, removed.userid); @@ -9576,11 +9585,12 @@ public class NotificationManagerService extends SystemService { public class NotificationListeners extends ManagedServices { static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners"; - static final String TAG_REQUESTED_LISTENERS = "req_listeners"; + static final String TAG_REQUESTED_LISTENERS = "request_listeners"; static final String TAG_REQUESTED_LISTENER = "listener"; static final String ATT_COMPONENT = "component"; static final String ATT_TYPES = "types"; - static final String ATT_PKGS = "pkgs"; + static final String ATT_PKG = "pkg"; + static final String ATT_UID = "uid"; static final String TAG_APPROVED = "allowed"; static final String TAG_DISALLOWED= "disallowed"; static final String XML_SEPARATOR = ","; @@ -9698,28 +9708,6 @@ public class NotificationManagerService extends SystemService { } @Override - public void onUserUnlocked(int user) { - int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA; - - final PackageManager pmWrapper = mContext.getPackageManager(); - List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser( - new Intent(getConfig().serviceInterface), flags, user); - - for (ResolveInfo resolveInfo : installedServices) { - ServiceInfo info = resolveInfo.serviceInfo; - - if (!getConfig().bindPermission.equals(info.permission)) { - continue; - } - Pair key = Pair.create(info.getComponentName(), user); - if (!mRequestedNotificationListeners.containsKey(key)) { - mRequestedNotificationListeners.put(key, new NotificationListenerFilter()); - } - } - super.onUserUnlocked(user); - } - - @Override public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) { super.onPackagesChanged(removingPackage, pkgList, uidList); @@ -9738,6 +9726,18 @@ public class NotificationManagerService extends SystemService { } } } + + // clean up anything in the disallowed pkgs list + for (int i = 0; i < pkgList.length; i++) { + String pkg = pkgList[i]; + int userId = UserHandle.getUserId(uidList[i]); + for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) { + NotificationListenerFilter nlf = mRequestedNotificationListeners.valueAt(j); + + VersionedPackage ai = new VersionedPackage(pkg, uidList[i]); + nlf.removePackage(ai); + } + } } @Override @@ -9767,15 +9767,17 @@ public class NotificationManagerService extends SystemService { int approved = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING | FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING; - ArraySet<String> disallowedPkgs = new ArraySet<>(); + ArraySet<VersionedPackage> disallowedPkgs = new ArraySet<>(); final int listenerOuterDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, listenerOuterDepth)) { if (TAG_APPROVED.equals(parser.getName())) { approved = XmlUtils.readIntAttribute(parser, ATT_TYPES); } else if (TAG_DISALLOWED.equals(parser.getName())) { - String pkgs = XmlUtils.readStringAttribute(parser, ATT_PKGS); - if (!TextUtils.isEmpty(pkgs)) { - disallowedPkgs = new ArraySet<>(pkgs.split(XML_SEPARATOR)); + String pkg = XmlUtils.readStringAttribute(parser, ATT_PKG); + int uid = XmlUtils.readIntAttribute(parser, ATT_UID); + if (!TextUtils.isEmpty(pkg)) { + VersionedPackage ai = new VersionedPackage(pkg, uid); + disallowedPkgs.add(ai); } } } @@ -9800,10 +9802,14 @@ public class NotificationManagerService extends SystemService { XmlUtils.writeIntAttribute(out, ATT_TYPES, nlf.getTypes()); out.endTag(null, TAG_APPROVED); - out.startTag(null, TAG_DISALLOWED); - XmlUtils.writeStringAttribute( - out, ATT_PKGS, String.join(XML_SEPARATOR, nlf.getDisallowedPackages())); - out.endTag(null, TAG_DISALLOWED); + for (VersionedPackage ai : nlf.getDisallowedPackages()) { + if (!TextUtils.isEmpty(ai.getPackageName())) { + out.startTag(null, TAG_DISALLOWED); + XmlUtils.writeStringAttribute(out, ATT_PKG, ai.getPackageName()); + XmlUtils.writeIntAttribute(out, ATT_UID, ai.getVersionCode()); + out.endTag(null, TAG_DISALLOWED); + } + } out.endTag(null, TAG_REQUESTED_LISTENER); } @@ -9821,6 +9827,38 @@ public class NotificationManagerService extends SystemService { mRequestedNotificationListeners.put(pair, nlf); } + @Override + protected void ensureFilters(ServiceInfo si, int userId) { + Pair listener = Pair.create(si.getComponentName(), userId); + NotificationListenerFilter existingNlf = + mRequestedNotificationListeners.get(listener); + if (existingNlf == null) { + // no stored filters for this listener; see if they provided a default + if (si.metaData != null) { + String typeList = si.metaData.getString( + NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES); + if (typeList != null) { + int types = 0; + String[] typeStrings = typeList.split(XML_SEPARATOR); + for (int i = 0; i < typeStrings.length; i++) { + if (TextUtils.isEmpty(typeStrings[i])) { + continue; + } + try { + types |= Integer.parseInt(typeStrings[i]); + } catch (NumberFormatException e) { + // skip + } + } + + NotificationListenerFilter nlf = + new NotificationListenerFilter(types, new ArraySet<>()); + mRequestedNotificationListeners.put(listener, nlf); + } + } + } + } + @GuardedBy("mNotificationLock") public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) { if (trim == TRIM_LIGHT) { diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java index b7b72d11a264..0b52c2e90ab9 100644 --- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java +++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java @@ -115,10 +115,10 @@ final class OverlayManagerShellCommand extends ShellCommand { out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME."); out.println(" disable [--user USER_ID] PACKAGE[:NAME]"); out.println(" Disable overlay within or owned by PACKAGE with optional unique NAME."); - out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE[:NAME]"); - out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME and"); - out.println(" disable all other overlays for its target package. If the --category"); - out.println(" option is given, only disables other overlays in the same category."); + out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE"); + out.println(" Enable overlay within or owned by PACKAGE and disable all other overlays"); + out.println(" for its target package. If the --category option is given, only disables"); + out.println(" other overlays in the same category."); out.println(" set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest"); out.println(" Change the priority of the overlay to be just higher than"); out.println(" the priority of PARENT If PARENT is the special keyword"); @@ -325,27 +325,12 @@ final class OverlayManagerShellCommand extends ShellCommand { return 1; } } - - final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired()); - final OverlayInfo overlayInfo = mInterface.getOverlayInfoByIdentifier(overlay, userId); - if (overlayInfo == null) { - err.println("Error: Unable to get overlay info of: " + overlay); - return 1; - } - - final List<OverlayInfo> overlaysForTarget = - mInterface.getOverlayInfosForTarget(overlayInfo.targetPackageName, userId); - final OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder(); - for (final OverlayInfo disableOverlay : overlaysForTarget) { - if ((inCategory && !Objects.equals(disableOverlay.category,overlayInfo.category)) - || !disableOverlay.isMutable) { - continue; - } - builder.setEnabled(disableOverlay.getOverlayIdentifier(), false, userId); + final String overlay = getNextArgRequired(); + if (inCategory) { + return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1; + } else { + return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1; } - builder.setEnabled(overlayInfo.getOverlayIdentifier(), true, userId); - mInterface.commit(builder.build()); - return 0; } private int runSetPriority() throws RemoteException { diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index de85d9e25642..f31d1da84c35 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -43,6 +43,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Singleton; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -226,6 +227,12 @@ public abstract class ApexManager { abstract ApexSessionInfo getStagedSessionInfo(int sessionId); /** + * Returns array of all staged sessions known to apexd. + */ + @NonNull + abstract SparseArray<ApexSessionInfo> getSessions(); + + /** * Submit a staged session to apex service. This causes the apex service to perform some initial * verification and accept or reject the session. Submitting a session successfully is not * enough for it to be activated at the next boot, the caller needs to call @@ -691,6 +698,21 @@ public abstract class ApexManager { } @Override + SparseArray<ApexSessionInfo> getSessions() { + try { + final ApexSessionInfo[] sessions = waitForApexService().getSessions(); + final SparseArray<ApexSessionInfo> result = new SparseArray<>(sessions.length); + for (int i = 0; i < sessions.length; i++) { + result.put(sessions[i].sessionId, sessions[i]); + } + return result; + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } + } + + @Override ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException { try { final ApexInfoList apexInfoList = new ApexInfoList(); @@ -1083,6 +1105,11 @@ public abstract class ApexManager { } @Override + SparseArray<ApexSessionInfo> getSessions() { + return new SparseArray<>(0); + } + + @Override ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException { throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index e91bb46657e1..b06e84d9df4e 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; @@ -935,6 +936,17 @@ public class LauncherAppsService extends SystemService { intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intents[0].setSourceBounds(sourceBounds); + // Replace theme for splash screen + final int splashScreenThemeResId = + mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(), + callingPackage, packageName, shortcutId, targetUserId); + if (splashScreenThemeResId != 0) { + if (startActivityOptions == null) { + startActivityOptions = new Bundle(); + } + startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId); + } + return startShortcutIntentsAsPublisher( intents, packageName, featureId, startActivityOptions, targetUserId); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 2d393c089411..281283048a95 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -295,23 +295,29 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - if (session.isStaged()) { - stagedSessionsToRestore.add(session.mStagedSession); + if (!session.isStaged()) { + continue; + } + StagingManager.StagedSession stagedSession = session.mStagedSession; + if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId() + && getSession(stagedSession.getParentSessionId()) == null) { + stagedSession.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "An orphan staged session " + stagedSession.sessionId() + " is found, " + + "parent " + stagedSession.getParentSessionId() + " is missing"); + continue; + } + if (!stagedSession.hasParentSessionId() && stagedSession.isCommitted() + && !stagedSession.isInTerminalState()) { + // StagingManager.restoreSessions expects a list of committed, non-finalized + // parent staged sessions. + stagedSessionsToRestore.add(stagedSession); } } } - // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK + // Don't hold mSessions lock when calling restoreSessions, since it might trigger an APK // atomic install which needs to query sessions, which requires lock on mSessions. - boolean isDeviceUpgrading = mPm.isDeviceUpgrading(); - for (StagingManager.StagedSession session : stagedSessionsToRestore) { - if (!session.isInTerminalState() && session.hasParentSessionId() - && getSession(session.getParentSessionId()) == null) { - session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "An orphan staged session " + session.sessionId() + " is found, " - + "parent " + session.getParentSessionId() + " is missing"); - } - mStagingManager.restoreSession(session, isDeviceUpgrading); - } + // Note: restoreSessions mutates content of stagedSessionsToRestore. + mStagingManager.restoreSessions(stagedSessionsToRestore, mPm.isDeviceUpgrading()); } @GuardedBy("mSessions") diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 9b092c000172..bb4ec16be0a8 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -112,6 +112,7 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_BITMAP_PATH = "bitmap-path"; private static final String ATTR_ICON_URI = "icon-uri"; private static final String ATTR_LOCUS_ID = "locus-id"; + private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id"; private static final String ATTR_PERSON_NAME = "name"; private static final String ATTR_PERSON_URI = "uri"; @@ -1654,6 +1655,7 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId()); ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName()); + ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId()); ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName()); @@ -1861,6 +1863,7 @@ class ShortcutPackage extends ShortcutPackageItem { String bitmapPath; String iconUri; final String locusIdString; + int splashScreenThemeResId; int backupVersionCode; ArraySet<String> categories = null; ArrayList<Person> persons = new ArrayList<>(); @@ -1871,6 +1874,8 @@ class ShortcutPackage extends ShortcutPackageItem { title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID); titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME); + splashScreenThemeResId = ShortcutService.parseIntAttribute(parser, + ATTR_SPLASH_SCREEN_THEME_ID); text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID); textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME); @@ -1964,7 +1969,8 @@ class ShortcutPackage extends ShortcutPackageItem { intents.toArray(new Intent[intents.size()]), rank, extras, lastChangedTimestamp, flags, iconResId, iconResName, bitmapPath, iconUri, - disabledReason, persons.toArray(new Person[persons.size()]), locusId); + disabledReason, persons.toArray(new Person[persons.size()]), locusId, + splashScreenThemeResId); } private static Intent parseIntent(TypedXmlPullParser parser) diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index d3aace19672a..c06f01a463ad 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -383,6 +383,8 @@ public class ShortcutParser { final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0); final int disabledMessageResId = sa.getResourceId( R.styleable.Shortcut_shortcutDisabledMessage, 0); + final int splashScreenTheme = sa.getResourceId( + R.styleable.Shortcut_splashScreenTheme, 0); if (TextUtils.isEmpty(id)) { Log.w(TAG, "android:shortcutId must be provided. activity=" + activity); @@ -404,7 +406,8 @@ public class ShortcutParser { disabledMessageResId, rank, iconResId, - enabled); + enabled, + splashScreenTheme); } finally { sa.recycle(); } @@ -413,7 +416,7 @@ public class ShortcutParser { private static ShortcutInfo createShortcutFromManifest(ShortcutService service, @UserIdInt int userId, String id, String packageName, ComponentName activityComponent, int titleResId, int textResId, int disabledMessageResId, - int rank, int iconResId, boolean enabled) { + int rank, int iconResId, boolean enabled, int splashScreenTheme) { final int flags = (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED) @@ -452,7 +455,8 @@ public class ShortcutParser { null, // icon Url disabledReason, null /* persons */, - null /* locusId */); + null /* locusId */, + splashScreenTheme); } private static String parseCategory(ShortcutService service, AttributeSet attrs) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 863e3fe5c6a3..4d8abea8acd4 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3214,6 +3214,32 @@ public class ShortcutService extends IShortcutService.Stub { } @Override + public int getShortcutStartingThemeResId(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull String shortcutId, int userId) { + Objects.requireNonNull(callingPackage, "callingPackage"); + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(shortcutId, "shortcutId"); + + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(); + + final ShortcutPackage p = getUserShortcutsLocked(userId) + .getPackageShortcutsIfExists(packageName); + if (p == null) { + return 0; + } + + final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); + return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0; + } + } + + @Override public ParcelFileDescriptor getShortcutIconFd(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 545567c26972..0a74032ab214 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -50,8 +50,6 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; -import android.os.storage.IStorageManager; -import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.IntArray; @@ -63,6 +61,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; @@ -143,10 +142,16 @@ public class StagingManager { } StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) { + this(context, packageParserSupplier, ApexManager.getInstance()); + } + + @VisibleForTesting + StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier, + ApexManager apexManager) { mContext = context; mPackageParserSupplier = packageParserSupplier; - mApexManager = ApexManager.getInstance(); + mApexManager = apexManager; mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPreRebootVerificationHandler = new PreRebootVerificationHandler( BackgroundThread.get().getLooper()); @@ -354,11 +359,11 @@ public class StagingManager { } // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device. - private void abortCheckpoint(int sessionId, String errorMsg) { - String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg; + private void abortCheckpoint(String failureReason, boolean supportsCheckpoint, + boolean needsCheckpoint) { Slog.e(TAG, failureReason); try { - if (supportsCheckpoint() && needsCheckpoint()) { + if (supportsCheckpoint && needsCheckpoint) { // Store failure reason for next reboot try (BufferedWriter writer = new BufferedWriter(new FileWriter(mFailureReasonFile))) { @@ -371,8 +376,9 @@ public class StagingManager { if (mApexManager.isApexSupported()) { mApexManager.revertActiveSessions(); } + PackageHelper.getStorageManager().abortChanges( - "StagingManager initiated", false /*retry*/); + "abort-staged-install", false /*retry*/); } } catch (Exception e) { Slog.wtf(TAG, "Failed to abort checkpoint", e); @@ -384,14 +390,6 @@ public class StagingManager { } } - private boolean supportsCheckpoint() throws RemoteException { - return PackageHelper.getStorageManager().supportsCheckpoint(); - } - - private boolean needsCheckpoint() throws RemoteException { - return PackageHelper.getStorageManager().needsCheckpoint(); - } - /** * Utility function for extracting apex sessions out of multi-package/single session. */ @@ -517,96 +515,31 @@ public class StagingManager { } } - private void resumeSession(@NonNull StagedSession session) - throws PackageManagerException { + private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint, + boolean needsCheckpoint) throws PackageManagerException { Slog.d(TAG, "Resuming session " + session.sessionId()); final boolean hasApex = session.containsApexSession(); - ApexSessionInfo apexSessionInfo = null; - if (hasApex) { - // Check with apexservice whether the apex packages have been activated. - apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId()); - - // Prepare for logging a native crash during boot, if one occurred. - if (apexSessionInfo != null && !TextUtils.isEmpty( - apexSessionInfo.crashingNativeProcess)) { - prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess); - } - - if (apexSessionInfo != null && apexSessionInfo.isVerified) { - // Session has been previously submitted to apexd, but didn't complete all the - // pre-reboot verification, perhaps because the device rebooted in the meantime. - // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as - // failed when not in checkpoint mode, hence it is being processed separately. - Slog.d(TAG, "Found pending staged session " + session.sessionId() + " still to " - + "be verified, resuming pre-reboot verification"); - mPreRebootVerificationHandler.startPreRebootVerification(session); - return; - } - } // Before we resume session, we check if revert is needed or not. Typically, we enter file- // system checkpoint mode when we reboot first time in order to install staged sessions. We // want to install staged sessions in this mode as rebooting now will revert user data. If // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will // have no effect on user data, so mark the sessions as failed instead. - try { - // If checkpoint is supported, then we only resume sessions if we are in checkpointing - // mode. If not, we fail all sessions. - if (supportsCheckpoint() && !needsCheckpoint()) { - String revertMsg = "Reverting back to safe state. Marking " - + session.sessionId() + " as failed."; - final String reasonForRevert = getReasonForRevert(); - if (!TextUtils.isEmpty(reasonForRevert)) { - revertMsg += " Reason for revert: " + reasonForRevert; - } - Slog.d(TAG, revertMsg); - session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg); - return; + // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode. + // If not, we fail all sessions. + if (supportsCheckpoint && !needsCheckpoint) { + String revertMsg = "Reverting back to safe state. Marking " + session.sessionId() + + " as failed."; + final String reasonForRevert = getReasonForRevert(); + if (!TextUtils.isEmpty(reasonForRevert)) { + revertMsg += " Reason for revert: " + reasonForRevert; } - } catch (RemoteException e) { - // Cannot continue staged install without knowing if fs-checkpoint is supported - Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session " - + session.sessionId(), e); - // TODO: Mark all staged sessions together and reboot only once - session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, - "Checkpoint support unknown. Aborting staged install."); - if (hasApex) { - mApexManager.revertActiveSessions(); - } - mPowerManager.reboot("Checkpoint support unknown"); + Slog.d(TAG, revertMsg); + session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg); return; } - // Check if apex packages in the session failed to activate - if (hasApex) { - if (apexSessionInfo == null) { - final String errorMsg = "apexd did not know anything about a staged session " - + "supposed to be activated"; - throw new PackageManagerException( - SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); - } - if (isApexSessionFailed(apexSessionInfo)) { - String errorMsg = "APEX activation failed. Check logcat messages from apexd " - + "for more information."; - if (!TextUtils.isEmpty(mNativeFailureReason)) { - errorMsg = "Session reverted due to crashing native process: " - + mNativeFailureReason; - } - throw new PackageManagerException( - SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); - } - if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) { - // Apexd did not apply the session for some unknown reason. There is no - // guarantee that apexd will install it next time. Safer to proactively mark - // it as failed. - final String errorMsg = "Staged session " + session.sessionId() + "at boot " - + "didn't activate nor fail. Marking it as failed anyway."; - throw new PackageManagerException( - SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); - } - } - // Handle apk and apk-in-apex installation if (hasApex) { checkInstallationOfApkInApexSuccessful(session); @@ -622,28 +555,24 @@ public class StagingManager { Slog.d(TAG, "Marking session " + session.sessionId() + " as applied"); session.setSessionApplied(); if (hasApex) { - try { - if (supportsCheckpoint()) { - // Store the session ID, which will be marked as successful by ApexManager - // upon boot completion. - synchronized (mSuccessfulStagedSessionIds) { - mSuccessfulStagedSessionIds.add(session.sessionId()); - } - } else { - // Mark sessions as successful immediately on non-checkpointing devices. - mApexManager.markStagedSessionSuccessful(session.sessionId()); + if (supportsCheckpoint) { + // Store the session ID, which will be marked as successful by ApexManager upon + // boot completion. + synchronized (mSuccessfulStagedSessionIds) { + mSuccessfulStagedSessionIds.add(session.sessionId()); } - } catch (RemoteException e) { - Slog.w(TAG, "Checkpoint support unknown, marking session as successful " - + "immediately."); + } else { + // Mark sessions as successful immediately on non-checkpointing devices. mApexManager.markStagedSessionSuccessful(session.sessionId()); } } } - void onInstallationFailure(StagedSession session, PackageManagerException e) { + void onInstallationFailure(StagedSession session, PackageManagerException e, + boolean supportsCheckpoint, boolean needsCheckpoint) { session.setSessionFailed(e.error, e.getMessage()); - abortCheckpoint(session.sessionId(), e.getMessage()); + abortCheckpoint("Failed to install sessionId: " + session.sessionId() + + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint); // If checkpoint is not supported, we have to handle failure for one staged session. if (!session.containsApexSession()) { @@ -767,8 +696,13 @@ public class StagingManager { "Cannot stage session " + session.sessionId() + " with package name null"); } - boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService( - Context.STORAGE_SERVICE)).isCheckpointSupported(); + boolean supportsCheckpoint; + try { + supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint(); + } catch (RemoteException e) { + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Can't query fs-checkpoint status : " + e); + } final boolean isRollback = isRollback(session); @@ -911,60 +845,166 @@ public class StagingManager { || apexSessionInfo.isRevertFailed; } - void restoreSession(@NonNull StagedSession session, boolean isDeviceUpgrading) { - if (session.hasParentSessionId()) { - // Only parent sessions can be restored - return; - } - // Store this parent session which will be used to check overlapping later - createSession(session); - // The preconditions used during pre-reboot verification might have changed when device - // is upgrading. Updated staged sessions to activation failed before we resume the session. - StagedSession sessionToResume = session; - if (isDeviceUpgrading && !sessionToResume.isInTerminalState()) { - sessionToResume.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "Build fingerprint has changed"); - return; + private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) { + int j = sessions.size(); + for (int i = 0; i < j; ) { + // Maintain following invariant: + // * elements at positions [0, i) should be kept + // * elements at positions [j, n) should be remove. + // * n = sessions.size() + StagedSession session = sessions.get(i); + if (session.isDestroyed()) { + // Device rebooted before abandoned session was cleaned up. + session.abandon(); + StagedSession session2 = sessions.set(j - 1, session); + sessions.set(i, session2); + j--; + } else if (!session.isSessionReady()) { + // The framework got restarted before the pre-reboot verification could complete, + // restart the verification. + mPreRebootVerificationHandler.startPreRebootVerification(session); + StagedSession session2 = sessions.set(j - 1, session); + sessions.set(i, session2); + j--; + } else { + i++; + } } - checkStateAndResume(sessionToResume); + // Delete last j elements. + sessions.subList(j, sessions.size()).clear(); } - private void checkStateAndResume(@NonNull StagedSession session) { - // Do not resume session if boot completed already + void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) { + // Do not resume sessions if boot completed already if (SystemProperties.getBoolean("sys.boot_completed", false)) { return; } - if (!session.isCommitted()) { - // Session hasn't been committed yet, ignore. + for (int i = 0; i < sessions.size(); i++) { + StagedSession session = sessions.get(i); + // Quick check that PackageInstallerService gave us sessions we expected. + Preconditions.checkArgument(!session.hasParentSessionId(), + session.sessionId() + " is a child session"); + Preconditions.checkArgument(session.isCommitted(), + session.sessionId() + " is not committed"); + Preconditions.checkArgument(!session.isInTerminalState(), + session.sessionId() + " is in terminal state"); + // Store this parent session which will be used to check overlapping later + createSession(session); + } + + if (isDeviceUpgrading) { + // TODO(ioffe): check that corresponding apex sessions are failed. + // The preconditions used during pre-reboot verification might have changed when device + // is upgrading. Fail all the sessions and exit early. + for (int i = 0; i < sessions.size(); i++) { + StagedSession session = sessions.get(i); + session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "Build fingerprint has changed"); + } return; } - // Check the state of the session and decide what to do next. - if (session.isSessionFailed() || session.isSessionApplied()) { - // Final states, nothing to do. + + boolean needsCheckpoint = false; + boolean supportsCheckpoint = false; + try { + supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint(); + needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint(); + } catch (RemoteException e) { + // This means that vold has crashed, and device is in a bad state. + throw new IllegalStateException("Failed to get checkpoint status", e); + } + + if (sessions.size() > 1 && !supportsCheckpoint) { + throw new IllegalStateException("Detected multiple staged sessions on a device without " + + "fs-checkpoint support"); + } + + // Do a set of quick checks before resuming individual sessions: + // 1. Schedule a pre-reboot verification for non-ready sessions. + // 2. Abandon destroyed sessions. + handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions| + + // 3. Check state of apex sessions is consistent. All non-applied sessions will be marked + // as failed. + final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions(); + boolean hasFailedApexSession = false; + boolean hasAppliedApexSession = false; + for (int i = 0; i < sessions.size(); i++) { + StagedSession session = sessions.get(i); + if (!session.containsApexSession()) { + // At this point we are only interested in apex sessions. + continue; + } + final ApexSessionInfo apexSession = apexSessions.get(session.sessionId()); + if (apexSession == null || apexSession.isUnknown) { + hasFailedApexSession = true; + session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did " + + "not know anything about a staged session supposed to be activated"); + continue; + } else if (isApexSessionFailed(apexSession)) { + hasFailedApexSession = true; + String errorMsg = "APEX activation failed. Check logcat messages from apexd " + + "for more information."; + if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) { + prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess); + errorMsg = "Session reverted due to crashing native process: " + + apexSession.crashingNativeProcess; + } + session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); + continue; + } else if (apexSession.isActivated || apexSession.isSuccess) { + hasAppliedApexSession = true; + continue; + } else if (apexSession.isStaged) { + // Apexd did not apply the session for some unknown reason. There is no guarantee + // that apexd will install it next time. Safer to proactively mark it as failed. + hasFailedApexSession = true; + session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "Staged session " + session.sessionId() + " at boot didn't activate nor " + + "fail. Marking it as failed anyway."); + } else { + Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state"); + hasFailedApexSession = true; + session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "Impossible state"); + } + } + + if (hasAppliedApexSession && hasFailedApexSession) { + abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint, + needsCheckpoint); return; } - if (session.isDestroyed()) { - // Device rebooted before abandoned session was cleaned up. - session.abandon(); + + if (hasFailedApexSession) { + // Either of those means that we failed at least one apex session, hence we should fail + // all other sessions. + for (int i = 0; i < sessions.size(); i++) { + StagedSession session = sessions.get(i); + if (session.isSessionFailed()) { + // Session has been already failed in the loop above. + continue; + } + session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "Another apex session failed"); + } return; } - if (!session.isSessionReady()) { - // The framework got restarted before the pre-reboot verification could complete, - // restart the verification. - mPreRebootVerificationHandler.startPreRebootVerification(session); - } else { - // Session had already being marked ready. Start the checks to verify if there is any - // follow-up work. + + // Time to resume sessions. + for (int i = 0; i < sessions.size(); i++) { + StagedSession session = sessions.get(i); try { - resumeSession(session); + resumeSession(session, supportsCheckpoint, needsCheckpoint); } catch (PackageManagerException e) { - onInstallationFailure(session, e); + onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint); } catch (Exception e) { Slog.e(TAG, "Staged install failed due to unhandled exception", e); onInstallationFailure(session, new PackageManagerException( SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "Staged install failed due to unhandled exception: " + e)); + "Staged install failed due to unhandled exception: " + e), + supportsCheckpoint, needsCheckpoint); } } } @@ -992,9 +1032,7 @@ public class StagingManager { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context ctx, Intent intent) { - mPreRebootVerificationHandler.readyToStart(); - BackgroundThread.getExecutor().execute( - () -> logFailedApexSessionsIfNecessary()); + onBootCompletedBroadcastReceived(); ctx.unregisterReceiver(this); } }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); @@ -1002,6 +1040,12 @@ public class StagingManager { mFailureReasonFile.delete(); } + @VisibleForTesting + void onBootCompletedBroadcastReceived() { + mPreRebootVerificationHandler.readyToStart(); + BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary()); + } + private static class LocalIntentReceiverSync { private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>(); @@ -1286,9 +1330,8 @@ public class StagingManager { private void handlePreRebootVerification_End(@NonNull StagedSession session) { // Before marking the session as ready, start checkpoint service if available try { - IStorageManager storageManager = PackageHelper.getStorageManager(); - if (storageManager.supportsCheckpoint()) { - storageManager.startCheckpoint(2); + if (PackageHelper.getStorageManager().supportsCheckpoint()) { + PackageHelper.getStorageManager().startCheckpoint(2); } } catch (Exception e) { // Failed to get hold of StorageManager diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS index 0e88862e01b1..e05ef482ec08 100644 --- a/services/core/java/com/android/server/pm/permission/OWNERS +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -1,4 +1,3 @@ -moltmann@google.com zhanghai@google.com per-file DefaultPermissionGrantPolicy.java = hackbod@android.com per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com @@ -7,5 +6,4 @@ per-file DefaultPermissionGrantPolicy.java = toddke@google.com per-file DefaultPermissionGrantPolicy.java = yamasani@google.com per-file DefaultPermissionGrantPolicy.java = patb@google.com per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com -per-file DefaultPermissionGrantPolicy.java = moltmann@google.com per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index e486f087535b..71e53d9f1f40 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2593,6 +2593,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { ArraySet<String> isPrivilegedPermissionAllowlisted = null; ArraySet<String> shouldGrantSignaturePermission = null; ArraySet<String> shouldGrantInternalPermission = null; + ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>(); final List<String> requestedPermissions = pkg.getRequestedPermissions(); final int requestedPermissionsSize = requestedPermissions.size(); for (int i = 0; i < requestedPermissionsSize; i++) { @@ -2613,14 +2614,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { isPrivilegedPermissionAllowlisted.add(permissionName); } if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission) - || shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) { + || shouldGrantPermissionByProtectionFlags(pkg, ps, permission, + shouldGrantPrivilegedPermissionIfWasGranted))) { if (shouldGrantSignaturePermission == null) { shouldGrantSignaturePermission = new ArraySet<>(); } shouldGrantSignaturePermission.add(permissionName); } if (permission.isInternal() - && shouldGrantPermissionByProtectionFlags(pkg, ps, permission)) { + && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, + shouldGrantPrivilegedPermissionIfWasGranted)) { if (shouldGrantInternalPermission == null) { shouldGrantInternalPermission = new ArraySet<>(); } @@ -2842,14 +2845,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { isPrivilegedPermissionAllowlisted, permName)) && (CollectionUtils.contains(shouldGrantSignaturePermission, permName) - || ((bp.isDevelopment() || bp.isRole()) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() || bp.isRole()) && origState.isPermissionGranted(permName)))) || (bp.isInternal() && (!bp.isPrivileged() || CollectionUtils.contains( isPrivilegedPermissionAllowlisted, permName)) && (CollectionUtils.contains(shouldGrantInternalPermission, permName) - || ((bp.isDevelopment() || bp.isRole()) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() || bp.isRole()) && origState.isPermissionGranted(permName))))) { // Grant an install permission. if (uidState.grantPermission(bp)) { @@ -3374,10 +3381,23 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) { return true; } + if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) { + return false; + } + // Updated system apps do not need to be allowlisted + if (packageSetting.getPkgState().isUpdatedSystemApp()) { + // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission + // can be granted, because an updated system app may be in a shared UID, and in case a + // new privileged permission is requested by the updated system app but not the factory + // app, although this app and permission combination isn't in the allowlist and can't + // get the permission this way, other apps in the shared UID may still get it. A proper + // fix for this would be to perform the reconciliation by UID, but for now let's keep + // the old workaround working, which is to keep granted privileged permissions still + // granted. + return true; + } // Only enforce the allowlist on boot - if (!mSystemReady - // Updated system apps do not need to be allowlisted - && !packageSetting.getPkgState().isUpdatedSystemApp()) { + if (!mSystemReady) { final ApexManager apexManager = ApexManager.getInstance(); final String containingApexPackageName = apexManager.getActiveApexPackageNameContainingPackage(packageName); @@ -3386,11 +3406,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { MATCH_ACTIVE_PACKAGE)); // Apps that are in updated apexs' do not need to be allowlisted if (!isInUpdatedApex) { - // it's only a reportable violation if the permission isn't explicitly - // denied - if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) { - return false; - } Slog.w(TAG, "Privileged permission " + permissionName + " for package " + packageName + " (" + pkg.getPath() + ") not in privapp-permissions allowlist"); @@ -3468,7 +3483,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg, - @NonNull PackageSetting pkgSetting, @NonNull Permission bp) { + @NonNull PackageSetting pkgSetting, @NonNull Permission bp, + @NonNull ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted) { boolean allowed = false; final boolean isPrivilegedPermission = bp.isPrivileged(); final boolean isOemPermission = bp.isOem(); @@ -3480,11 +3496,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { final PackageSetting disabledPs = mPackageManagerInt .getDisabledSystemPackage(pkg.getPackageName()); final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg; - if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains( - permissionName)) { - allowed = (isPrivilegedPermission && disabledPkg.isPrivileged()) - || (isOemPermission && canGrantOemPermission(disabledPkg, - permissionName)); + if (disabledPkg != null + && ((isPrivilegedPermission && disabledPkg.isPrivileged()) + || (isOemPermission && canGrantOemPermission(disabledPkg, + permissionName)))) { + if (disabledPkg.getRequestedPermissions().contains(permissionName)) { + allowed = true; + } else { + // If the original was granted this permission, we take + // that grant decision as read and propagate it to the + // update. + shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName); + } } } else { allowed = (isPrivilegedPermission && pkg.isPrivileged()) diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index 36efb39909a6..080de73ff933 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -21,11 +21,8 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedIntentInfo; -import android.os.Binder; import android.os.Build; import android.util.ArraySet; import android.util.Patterns; @@ -36,19 +33,31 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import java.util.List; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class DomainVerificationCollector { + // The default domain name matcher doesn't account for wildcards, so prefix with *. + private static final Pattern DOMAIN_NAME_WITH_WILDCARD = + Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern()); + @NonNull private final PlatformCompat mPlatformCompat; @NonNull private final SystemConfig mSystemConfig; + @NonNull + private final Matcher mDomainMatcher; + public DomainVerificationCollector(@NonNull PlatformCompat platformCompat, @NonNull SystemConfig systemConfig) { mPlatformCompat = platformCompat; mSystemConfig = systemConfig; + + // Cache the matcher to avoid calling into native on each check + mDomainMatcher = DOMAIN_NAME_WITH_WILDCARD.matcher(""); } /** @@ -144,7 +153,10 @@ public class DomainVerificationCollector { if (intent.handlesWebUris(false)) { int authorityCount = intent.countDataAuthorities(); for (int index = 0; index < authorityCount; index++) { - domains.add(intent.getDataAuthority(index).getHost()); + String host = intent.getDataAuthority(index).getHost(); + if (isValidHost(host)) { + domains.add(host); + } } } } @@ -188,13 +200,22 @@ public class DomainVerificationCollector { int authorityCount = intent.countDataAuthorities(); for (int index = 0; index < authorityCount; index++) { String host = intent.getDataAuthority(index).getHost(); - // It's easy to misconfigure autoVerify intent filters, so to avoid - // adding unintended hosts, check if the host is an HTTP domain. - if (Patterns.DOMAIN_NAME.matcher(host).matches()) { + if (isValidHost(host)) { domains.add(host); } } } } } + + /** + * It's easy to mis-configure autoVerify intent filters, so to avoid adding unintended hosts, + * check if the host is an HTTP domain. This applies for both legacy and modern versions of + * the API, which will strip invalid hosts from the legacy parsing result. This is done to + * improve the reliability of any legacy verifiers. + */ + private boolean isValidHost(String host) { + mDomainMatcher.reset(host); + return mDomainMatcher.matches(); + } } diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java index 9389e63404f4..a80406548719 100644 --- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java +++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java @@ -30,7 +30,6 @@ import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; import android.os.Process; import android.os.UserHandle; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; @@ -45,6 +44,7 @@ import com.android.server.pm.verify.domain.DomainVerificationMessageCodes; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -168,22 +168,58 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { return true; } - Set<String> successfulDomains = new ArraySet<>(info.getHostToStateMap().keySet()); - successfulDomains.removeAll(response.failedDomains); + AndroidPackage pkg = mConnection.getPackage(packageName); + if (pkg == null) { + return true; + } + + ArraySet<String> failedDomains = new ArraySet<>(response.failedDomains); + Map<String, Integer> hostToStateMap = info.getHostToStateMap(); + Set<String> hostKeySet = hostToStateMap.keySet(); + ArraySet<String> successfulDomains = new ArraySet<>(hostKeySet); + successfulDomains.removeAll(failedDomains); + + // v1 doesn't handle wildcard domains, so check them here for the verifier + int size = successfulDomains.size(); + for (int index = size - 1; index >= 0; index--) { + String domain = successfulDomains.valueAt(index); + if (domain.startsWith("*.")) { + String nonWildcardDomain = domain.substring(2); + if (failedDomains.contains(nonWildcardDomain)) { + failedDomains.add(domain); + successfulDomains.removeAt(index); + + // It's possible to declare a wildcard without declaring its + // non-wildcard equivalent, so if it wasn't originally declared, + // remove the transformed domain from the failed set. Otherwise the + // manager will not accept the failed set as it contains an undeclared + // domain. + if (!hostKeySet.contains(nonWildcardDomain)) { + failedDomains.remove(nonWildcardDomain); + } + } + } + } int callingUid = response.callingUid; - try { - mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, - successfulDomains, DomainVerificationState.STATE_SUCCESS); - } catch (DomainVerificationManager.InvalidDomainSetException - | PackageManager.NameNotFoundException ignored) { + if (!successfulDomains.isEmpty()) { + try { + mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, + successfulDomains, DomainVerificationState.STATE_SUCCESS); + } catch (DomainVerificationManager.InvalidDomainSetException + | PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Failure reporting successful domains for " + packageName, e); + } } - try { - mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, - new ArraySet<>(response.failedDomains), - DomainVerificationState.STATE_LEGACY_FAILURE); - } catch (DomainVerificationManager.InvalidDomainSetException - | PackageManager.NameNotFoundException ignored) { + + if (!failedDomains.isEmpty()) { + try { + mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, + failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE); + } catch (DomainVerificationManager.InvalidDomainSetException + | PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Failure reporting failed domains for " + packageName, e); + } } return true; @@ -235,7 +271,21 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion, // not the version of the verification agent on device. ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg); - return TextUtils.join(" ", domains); + + // v1 doesn't handle wildcard domains, so transform them here to the root + StringBuilder builder = new StringBuilder(); + int size = domains.size(); + for (int index = 0; index < size; index++) { + if (index > 0) { + builder.append(" "); + } + String domain = domains.valueAt(index); + if (domain.startsWith("*.")) { + domain = domain.substring(2); + } + builder.append(domain); + } + return builder.toString(); } private static class Response { diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index ac358db51939..4e1065a9d3af 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -17,6 +17,7 @@ package com.android.server.policy; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -84,7 +85,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true; @VisibleForTesting - static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT"); + static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE, + "DEFAULT"); private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/"; private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/"; diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index ea41980c02b1..b7285d58af4b 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -73,6 +73,8 @@ public class PowerStatsService extends SystemService { private TimerTrigger mTimerTrigger; @Nullable private StatsPullAtomCallbackImpl mPullAtomCallback; + @Nullable + private PowerStatsInternal mPowerStatsInternal; @VisibleForTesting static class Injector { @@ -125,8 +127,8 @@ public class PowerStatsService extends SystemService { } StatsPullAtomCallbackImpl createStatsPullerImpl(Context context, - IPowerStatsHALWrapper powerStatsHALWrapper) { - return new StatsPullAtomCallbackImpl(context, powerStatsHALWrapper); + PowerStatsInternal powerStatsInternal) { + return new StatsPullAtomCallbackImpl(context, powerStatsInternal); } } @@ -175,21 +177,14 @@ public class PowerStatsService extends SystemService { @Override public void onStart() { if (getPowerStatsHal().isInitialized()) { - // Only create internal service if PowerStatsHal is available. - publishLocalService(PowerStatsInternal.class, new LocalService()); + mPowerStatsInternal = new LocalService(); + publishLocalService(PowerStatsInternal.class, mPowerStatsInternal); } publishBinderService(Context.POWER_STATS_SERVICE, new BinderService()); } private void onSystemServicesReady() { - if (getPowerStatsHal().isInitialized()) { - if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers"); - - // Only start statsd pullers if initialization is successful. - mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, getPowerStatsHal()); - } else { - Slog.e(TAG, "Failed to start PowerStatsService statsd pullers"); - } + mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal); } private void onBootCompleted() { diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java index 7c6999acc666..bdabefbbeee6 100644 --- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java +++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java @@ -24,26 +24,31 @@ import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.State; import android.hardware.power.stats.StateResidency; import android.hardware.power.stats.StateResidencyResult; +import android.power.PowerStatsInternal; +import android.util.Slog; import android.util.StatsEvent; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FrameworkStatsLog; -import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * StatsPullAtomCallbackImpl is responsible implementing the stats pullers for * SUBSYSTEM_SLEEP_STATE and ON_DEVICE_POWER_MEASUREMENT statsd atoms. */ public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { + private static final String TAG = StatsPullAtomCallbackImpl.class.getSimpleName(); private Context mContext; - private IPowerStatsHALWrapper mPowerStatsHALWrapper; + private PowerStatsInternal mPowerStatsInternal; private Map<Integer, Channel> mChannels = new HashMap(); private Map<Integer, String> mEntityNames = new HashMap(); - private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();; + private Map<Integer, Map<Integer, String>> mStateNames = new HashMap(); + private static final int STATS_PULL_TIMEOUT_MILLIS = 2000; + private static final boolean DEBUG = false; @Override public int onPullAtom(int atomTag, List<StatsEvent> data) { @@ -57,21 +62,28 @@ public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCall } } - private void initPullOnDevicePowerMeasurement() { - Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo(); - if (channels == null) { - return; + private boolean initPullOnDevicePowerMeasurement() { + Channel[] channels = mPowerStatsInternal.getEnergyMeterInfo(); + if (channels == null || channels.length == 0) { + Slog.e(TAG, "Failed to init OnDevicePowerMeasurement puller"); + return false; } for (int i = 0; i < channels.length; i++) { final Channel channel = channels[i]; mChannels.put(channel.id, channel); } + + return true; } private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) { - EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]); - if (energyMeasurements == null) { + final EnergyMeasurement[] energyMeasurements; + try { + energyMeasurements = mPowerStatsInternal.readEnergyMeterAsync(new int[0]) + .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (Exception e) { + Slog.e(TAG, "Failed to readEnergyMeterAsync", e); return StatsManager.PULL_SKIP; } @@ -91,10 +103,11 @@ public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCall return StatsManager.PULL_SUCCESS; } - private void initSubsystemSleepState() { - PowerEntity[] entities = mPowerStatsHALWrapper.getPowerEntityInfo(); - if (entities == null) { - return; + private boolean initSubsystemSleepState() { + PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo(); + if (entities == null || entities.length == 0) { + Slog.e(TAG, "Failed to init SubsystemSleepState puller"); + return false; } for (int i = 0; i < entities.length; i++) { @@ -108,13 +121,20 @@ public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCall mEntityNames.put(entity.id, entity.name); mStateNames.put(entity.id, states); } + + return true; } private int pullSubsystemSleepState(int atomTag, List<StatsEvent> events) { - StateResidencyResult[] results = mPowerStatsHALWrapper.getStateResidency(new int[0]); - if (results == null) { + final StateResidencyResult[] results; + try { + results = mPowerStatsInternal.getStateResidencyAsync(new int[0]) + .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (Exception e) { + Slog.e(TAG, "Failed to getStateResidencyAsync", e); return StatsManager.PULL_SKIP; } + for (int i = 0; i < results.length; i++) { final StateResidencyResult result = results[i]; for (int j = 0; j < result.stateResidencyData.length; j++) { @@ -131,22 +151,33 @@ public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCall return StatsManager.PULL_SUCCESS; } - public StatsPullAtomCallbackImpl(Context context, IPowerStatsHALWrapper powerStatsHALWrapper) { + public StatsPullAtomCallbackImpl(Context context, PowerStatsInternal powerStatsInternal) { + if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers"); + mContext = context; - mPowerStatsHALWrapper = powerStatsHALWrapper; - initPullOnDevicePowerMeasurement(); - initSubsystemSleepState(); + mPowerStatsInternal = powerStatsInternal; + + if (powerStatsInternal == null) { + Slog.e(TAG, "Failed to start PowerStatsService statsd pullers"); + return; + } StatsManager manager = mContext.getSystemService(StatsManager.class); - manager.setPullAtomCallback( - FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE, - null, // use default PullAtomMetadata values - ConcurrentUtils.DIRECT_EXECUTOR, - this); - manager.setPullAtomCallback( - FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT, - null, // use default PullAtomMetadata values - ConcurrentUtils.DIRECT_EXECUTOR, - this); + + if (initPullOnDevicePowerMeasurement()) { + manager.setPullAtomCallback( + FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT, + null, // use default PullAtomMetadata values + ConcurrentUtils.DIRECT_EXECUTOR, + this); + } + + if (initSubsystemSleepState()) { + manager.setPullAtomCallback( + FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE, + null, // use default PullAtomMetadata values + ConcurrentUtils.DIRECT_EXECUTOR, + this); + } } } diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS index b94d98827d71..31e3549d9111 100644 --- a/services/core/java/com/android/server/role/OWNERS +++ b/services/core/java/com/android/server/role/OWNERS @@ -1,5 +1,4 @@ svetoslavganov@google.com -moltmann@google.com zhanghai@google.com evanseverson@google.com eugenesusla@google.com diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 539b4138cc18..15c72b34dbc0 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -139,6 +139,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BinderCallsStats.ExportedCallStat; +import com.android.internal.os.KernelCpuBpfTracking; import com.android.internal.os.KernelCpuThreadReader; import com.android.internal.os.KernelCpuThreadReaderDiff; import com.android.internal.os.KernelCpuThreadReaderSettingsObserver; @@ -1455,7 +1456,7 @@ public class StatsPullAtomService extends SystemService { } private void registerCpuTimePerClusterFreq() { - if (KernelCpuTotalBpfMapReader.isSupported()) { + if (KernelCpuBpfTracking.isSupported()) { int tagId = FrameworkStatsLog.CPU_TIME_PER_CLUSTER_FREQ; PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {3}) @@ -1612,9 +1613,6 @@ public class StatsPullAtomService extends SystemService { // Aggregate times for the same uids. SparseArray<long[]> aggregated = new SparseArray<>(); mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> { - // For uids known to be aggregated from many entries allow mutating in place to avoid - // many copies. Otherwise, copy before aggregating. - boolean mutateInPlace = false; if (UserHandle.isIsolated(uid)) { // Skip individual isolated uids because they are recycled and quickly removed from // the underlying data source. @@ -1622,26 +1620,18 @@ public class StatsPullAtomService extends SystemService { } else if (UserHandle.isSharedAppGid(uid)) { // All shared app gids are accounted together. uid = LAST_SHARED_APPLICATION_GID; - mutateInPlace = true; } else { // Everything else is accounted under their base uid. uid = UserHandle.getAppId(uid); } long[] aggCpuFreqTimeMs = aggregated.get(uid); - if (aggCpuFreqTimeMs != null) { - if (!mutateInPlace) { - aggCpuFreqTimeMs = Arrays.copyOf(aggCpuFreqTimeMs, cpuFreqTimeMs.length); - aggregated.put(uid, aggCpuFreqTimeMs); - } - for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) { - aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex]; - } - } else { - if (mutateInPlace) { - cpuFreqTimeMs = Arrays.copyOf(cpuFreqTimeMs, cpuFreqTimeMs.length); - } - aggregated.put(uid, cpuFreqTimeMs); + if (aggCpuFreqTimeMs == null) { + aggCpuFreqTimeMs = new long[cpuFreqTimeMs.length]; + aggregated.put(uid, aggCpuFreqTimeMs); + } + for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) { + aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex]; } }); @@ -1660,17 +1650,18 @@ public class StatsPullAtomService extends SystemService { } private void registerCpuCyclesPerThreadGroupCluster() { - // TODO(b/173227907): Register only when supported. - int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER; - PullAtomMetadata metadata = new PullAtomMetadata.Builder() - .setAdditiveFields(new int[] {3, 4}) - .build(); - mStatsManager.setPullAtomCallback( - tagId, - metadata, - DIRECT_EXECUTOR, - mStatsCallbackImpl - ); + if (KernelCpuBpfTracking.isSupported()) { + int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER; + PullAtomMetadata metadata = new PullAtomMetadata.Builder() + .setAdditiveFields(new int[] {3, 4}) + .build(); + mStatsManager.setPullAtomCallback( + tagId, + metadata, + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } } int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) { @@ -1729,7 +1720,7 @@ public class StatsPullAtomService extends SystemService { } for (int cluster = 0; cluster < clusters; ++cluster) { pulledData.add(FrameworkStatsLog.buildStatsEvent( - atomTag, threadGroup, cluster, aggregatedCycles[cluster], + atomTag, threadGroup, cluster, aggregatedCycles[cluster] / 1_000_000L, aggregatedTimesUs[cluster] / 1_000)); } } diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java index eb4a0501a953..0087c0c29853 100644 --- a/services/core/java/com/android/server/storage/StorageSessionController.java +++ b/services/core/java/com/android/server/storage/StorageSessionController.java @@ -158,6 +158,29 @@ public final class StorageSessionController { } /** + * Called when {@code packageName} is about to ANR + * + * @return ANR dialog delay in milliseconds + */ + public long getAnrDelayMillis(String packageName, int uid) + throws ExternalStorageServiceException { + synchronized (mLock) { + int size = mConnections.size(); + for (int i = 0; i < size; i++) { + int key = mConnections.keyAt(i); + StorageUserConnection connection = mConnections.get(key); + if (connection != null) { + long delay = connection.getAnrDelayMillis(packageName, uid); + if (delay > 0) { + return delay; + } + } + } + } + return 0; + } + + /** * Removes and returns the {@link StorageUserConnection} for {@code vol}. * * Does nothing if {@link #shouldHandle} is {@code false} diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index 13cceeed84e6..709d558ea0bc 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -16,6 +16,7 @@ package com.android.server.storage; +import static android.service.storage.ExternalStorageService.EXTRA_ANR_TIMEOUT_MS; import static android.service.storage.ExternalStorageService.EXTRA_ERROR; import static android.service.storage.ExternalStorageService.FLAG_SESSION_ATTRIBUTE_INDEXABLE; import static android.service.storage.ExternalStorageService.FLAG_SESSION_TYPE_FUSE; @@ -143,6 +144,24 @@ public final class StorageUserConnection { } /** + * Called when {@code packageName} is about to ANR + * + * @return ANR dialog delay in milliseconds + */ + public long getAnrDelayMillis(String packageName, int uid) + throws ExternalStorageServiceException { + synchronized (mSessionsLock) { + for (String sessionId : mSessions.keySet()) { + long delay = mActiveConnection.getAnrDelayMillis(packageName, uid); + if (delay > 0) { + return delay; + } + } + } + return 0; + } + + /** * Removes a session without ending it or waiting for exit. * * This should only be used if the session has certainly been ended because the volume was @@ -234,6 +253,9 @@ public final class StorageUserConnection { @GuardedBy("mLock") private final ArrayList<CompletableFuture<Void>> mOutstandingOps = new ArrayList<>(); + @GuardedBy("mLock") + private final ArrayList<CompletableFuture<Long>> mOutstandingTimeoutOps = new ArrayList<>(); + @Override public void close() { ServiceConnection oldConnection = null; @@ -250,6 +272,9 @@ public final class StorageUserConnection { for (CompletableFuture<Void> op : mOutstandingOps) { op.cancel(true); } + for (CompletableFuture<Long> op : mOutstandingTimeoutOps) { + op.cancel(true); + } mOutstandingOps.clear(); } @@ -264,27 +289,44 @@ public final class StorageUserConnection { } } - private void waitForAsync(AsyncStorageServiceCall asyncCall) throws Exception { - CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded(); + private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception { CompletableFuture<Void> opFuture = new CompletableFuture<>(); + RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture)); + + waitForAsync(asyncCall, callback, opFuture, mOutstandingOps, + DEFAULT_REMOTE_TIMEOUT_SECONDS); + } + + private long waitForAsyncLong(AsyncStorageServiceCall asyncCall) throws Exception { + CompletableFuture<Long> opFuture = new CompletableFuture<>(); + RemoteCallback callback = + new RemoteCallback(result -> setTimeoutResult(result, opFuture)); + + return waitForAsync(asyncCall, callback, opFuture, mOutstandingTimeoutOps, + 1 /* timeoutSeconds */); + } + + private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback, + CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps, + long timeoutSeconds) throws Exception { + CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded(); try { synchronized (mLock) { - mOutstandingOps.add(opFuture); + outstandingOps.add(opFuture); } - serviceFuture.thenCompose(service -> { + return serviceFuture.thenCompose(service -> { try { - asyncCall.run(service, - new RemoteCallback(result -> setResult(result, opFuture))); + asyncCall.run(service, callback); } catch (RemoteException e) { opFuture.completeExceptionally(e); } return opFuture; - }).get(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS); + }).get(timeoutSeconds, TimeUnit.SECONDS); } finally { synchronized (mLock) { - mOutstandingOps.remove(opFuture); + outstandingOps.remove(opFuture); } } } @@ -292,9 +334,9 @@ public final class StorageUserConnection { public void startSession(Session session, ParcelFileDescriptor fd) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> service.startSession(session.sessionId, + waitForAsyncVoid((service, callback) -> service.startSession(session.sessionId, FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE, - fd, session.upperPath, session.lowerPath, callback)); + fd, session.upperPath, session.lowerPath, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to start session: " + session, e); } finally { @@ -308,7 +350,7 @@ public final class StorageUserConnection { public void endSession(Session session) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> + waitForAsyncVoid((service, callback) -> service.endSession(session.sessionId, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to end session: " + session, e); @@ -319,7 +361,7 @@ public final class StorageUserConnection { public void notifyVolumeStateChanged(String sessionId, StorageVolume vol) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> + waitForAsyncVoid((service, callback) -> service.notifyVolumeStateChanged(sessionId, vol, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to notify volume state changed " @@ -330,7 +372,7 @@ public final class StorageUserConnection { public void freeCache(String sessionId, String volumeUuid, long bytes) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> + waitForAsyncVoid((service, callback) -> service.freeCache(sessionId, volumeUuid, bytes, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to free " + bytes @@ -338,6 +380,27 @@ public final class StorageUserConnection { } } + public long getAnrDelayMillis(String packgeName, int uid) + throws ExternalStorageServiceException { + try { + return waitForAsyncLong((service, callback) -> + service.getAnrDelayMillis(packgeName, uid, callback)); + } catch (Exception e) { + throw new ExternalStorageServiceException("Failed to notify app not responding: " + + packgeName, e); + } + } + + private void setTimeoutResult(Bundle result, CompletableFuture<Long> future) { + ParcelableException ex = result.getParcelable(EXTRA_ERROR); + if (ex != null) { + future.completeExceptionally(ex); + } else { + long timeoutMs = result.getLong(EXTRA_ANR_TIMEOUT_MS); + future.complete(timeoutMs); + } + } + private void setResult(Bundle result, CompletableFuture<Void> future) { ParcelableException ex = result.getParcelable(EXTRA_ERROR); if (ex != null) { diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 2503e812f9e1..8aab3a66ada3 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -27,6 +27,7 @@ import static com.android.server.VcnManagementService.VDBG; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; @@ -58,6 +59,9 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.SystemClock; import android.util.ArraySet; import android.util.Slog; @@ -65,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; @@ -120,15 +125,34 @@ import java.util.concurrent.TimeUnit; * +----------------------------+ * </pre> * + * <p>All messages in VcnGatewayConnection <b>should</b> be enqueued using {@link + * #sendMessageAndAcquireWakeLock}. Careful consideration should be given to any uses of {@link + * #sendMessage} directly, as they are not guaranteed to be processed in a timely manner (due to the + * lack of WakeLocks). + * + * <p>Any attempt to remove messages from the Handler should be done using {@link + * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when + * no messages remain in the Handler queue. + * * @hide */ public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM"; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM"; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM"; + private static final int[] MERGED_CAPABILITIES = new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; - - private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: "; @@ -137,7 +161,8 @@ public class VcnGatewayConnection extends StateMachine { private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel"; private static final int TOKEN_ALL = Integer.MIN_VALUE; - private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; @VisibleForTesting(visibility = Visibility.PRIVATE) static final int TEARDOWN_TIMEOUT_SECONDS = 5; @@ -412,11 +437,26 @@ public class VcnGatewayConnection extends StateMachine { @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull private final Dependencies mDeps; - @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback; @NonNull private final IpSecManager mIpSecManager; - @NonNull private final IpSecTunnelInterface mTunnelIface; + + @Nullable private IpSecTunnelInterface mTunnelIface = null; + + /** + * WakeLock to be held when processing messages on the Handler queue. + * + * <p>Used to prevent the device from going to sleep while there are VCN-related events to + * process for this VcnGatewayConnection. + * + * <p>Obtain a WakeLock when enquing messages onto the Handler queue. Once all messages in the + * Handler queue have been processed, the WakeLock can be released and cleared. + * + * <p>This WakeLock is also used for handling delayed messages by using WakeupMessages to send + * delayed messages to the Handler. When the WakeupMessage fires, it will obtain the WakeLock + * before enquing the delayed event to the Handler. + */ + @NonNull private final VcnWakeLock mWakeLock; /** Running state of this VcnGatewayConnection. */ private boolean mIsRunning = true; @@ -482,6 +522,10 @@ public class VcnGatewayConnection extends StateMachine { */ private NetworkAgent mNetworkAgent; + @Nullable private WakeupMessage mTeardownTimeoutAlarm; + @Nullable private WakeupMessage mDisconnectRequestAlarm; + @Nullable private WakeupMessage mRetryTimeoutAlarm; + public VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @@ -517,6 +561,9 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); + mWakeLock = + mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG); + mUnderlyingNetworkTracker = mDeps.newUnderlyingNetworkTracker( mVcnContext, @@ -526,20 +573,6 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkTrackerCallback); mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); - IpSecTunnelInterface iface; - try { - iface = - mIpSecManager.createIpSecTunnelInterface( - DUMMY_ADDR, DUMMY_ADDR, new Network(-1)); - } catch (IOException | ResourceUnavailableException e) { - teardownAsynchronously(); - mTunnelIface = null; - - return; - } - - mTunnelIface = iface; - addState(mDisconnectedState); addState(mDisconnectingState); addState(mConnectingState); @@ -557,7 +590,7 @@ public class VcnGatewayConnection extends StateMachine { * <p>Once torn down, this VcnTunnel CANNOT be started again. */ public void teardownAsynchronously() { - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); @@ -573,6 +606,12 @@ public class VcnGatewayConnection extends StateMachine { mTunnelIface.close(); } + releaseWakeLock(); + + cancelTeardownTimeoutAlarm(); + cancelDisconnectRequestAlarm(); + cancelRetryTimeoutAlarm(); + mUnderlyingNetworkTracker.teardown(); } @@ -589,79 +628,300 @@ public class VcnGatewayConnection extends StateMachine { mLastSnapshot = snapshot; mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); - sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); + sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); } private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { @Override public void onSelectedUnderlyingNetworkChanged( @Nullable UnderlyingNetworkRecord underlying) { + // TODO(b/180132994): explore safely removing this Thread check + mVcnContext.ensureRunningOnLooperThread(); + // TODO(b/179091925): Move the delayed-message handling to BaseState // If underlying is null, all underlying networks have been lost. Disconnect VCN after a // timeout. if (underlying == null) { - sendMessageDelayed( - EVENT_DISCONNECT_REQUESTED, - TOKEN_ALL, - new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST), - TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS)); - } else if (getHandler() != null) { - // Cancel any existing disconnect due to loss of underlying network - // getHandler() can return null if the state machine has already quit. Since this is - // called from other classes, this condition must be verified. - - getHandler() - .removeEqualMessages( - EVENT_DISCONNECT_REQUESTED, - new EventDisconnectRequestedInfo( - DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + setDisconnectRequestAlarm(); + } else { + // Received a new Network so any previous alarm is irrelevant - cancel + clear it, + // and cancel any queued EVENT_DISCONNECT_REQUEST messages + cancelDisconnectRequestAlarm(); } - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_UNDERLYING_NETWORK_CHANGED, TOKEN_ALL, new EventUnderlyingNetworkChangedInfo(underlying)); } } - private void sendMessage(int what, int token, EventInfo data) { + private void acquireWakeLock() { + mVcnContext.ensureRunningOnLooperThread(); + + if (mIsRunning) { + mWakeLock.acquire(); + } + } + + private void releaseWakeLock() { + mVcnContext.ensureRunningOnLooperThread(); + + mWakeLock.release(); + } + + /** + * Attempt to release mWakeLock - this can only be done if the Handler is null (meaning the + * StateMachine has been shutdown and thus has no business keeping the WakeLock) or if there are + * no more messags left to process in the Handler queue (at which point the WakeLock can be + * released until more messages must be processed). + */ + private void maybeReleaseWakeLock() { + final Handler handler = getHandler(); + if (handler == null || !handler.hasMessagesOrCallbacks()) { + releaseWakeLock(); + } + } + + @Override + public void sendMessage(int what) { + Slog.wtf( + TAG, + "sendMessage should not be used in VcnGatewayConnection. See" + + " sendMessageAndAcquireWakeLock()"); + super.sendMessage(what); + } + + @Override + public void sendMessage(int what, Object obj) { + Slog.wtf( + TAG, + "sendMessage should not be used in VcnGatewayConnection. See" + + " sendMessageAndAcquireWakeLock()"); + super.sendMessage(what, obj); + } + + @Override + public void sendMessage(int what, int arg1) { + Slog.wtf( + TAG, + "sendMessage should not be used in VcnGatewayConnection. See" + + " sendMessageAndAcquireWakeLock()"); + super.sendMessage(what, arg1); + } + + @Override + public void sendMessage(int what, int arg1, int arg2) { + Slog.wtf( + TAG, + "sendMessage should not be used in VcnGatewayConnection. See" + + " sendMessageAndAcquireWakeLock()"); + super.sendMessage(what, arg1, arg2); + } + + @Override + public void sendMessage(int what, int arg1, int arg2, Object obj) { + Slog.wtf( + TAG, + "sendMessage should not be used in VcnGatewayConnection. See" + + " sendMessageAndAcquireWakeLock()"); + super.sendMessage(what, arg1, arg2, obj); + } + + @Override + public void sendMessage(Message msg) { + Slog.wtf( + TAG, + "sendMessage should not be used in VcnGatewayConnection. See" + + " sendMessageAndAcquireWakeLock()"); + super.sendMessage(msg); + } + + // TODO(b/180146061): also override and Log.wtf() other Message handling methods + // In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and + // removeDeferredMessages + + /** + * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not + * go to sleep before processing the sent message. + */ + private void sendMessageAndAcquireWakeLock(int what, int token) { + acquireWakeLock(); + super.sendMessage(what, token); + } + + /** + * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not + * go to sleep before processing the sent message. + */ + private void sendMessageAndAcquireWakeLock(int what, int token, EventInfo data) { + acquireWakeLock(); super.sendMessage(what, token, ARG_NOT_PRESENT, data); } - private void sendMessage(int what, int token, int arg2, EventInfo data) { + /** + * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not + * go to sleep before processing the sent message. + */ + private void sendMessageAndAcquireWakeLock(int what, int token, int arg2, EventInfo data) { + acquireWakeLock(); super.sendMessage(what, token, arg2, data); } - private void sendMessageDelayed(int what, int token, EventInfo data, long timeout) { - super.sendMessageDelayed(what, token, ARG_NOT_PRESENT, data, timeout); + /** + * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not + * go to sleep before processing the sent message. + */ + private void sendMessageAndAcquireWakeLock(Message msg) { + acquireWakeLock(); + super.sendMessage(msg); + } + + /** + * Removes all messages matching the given parameters, and attempts to release mWakeLock if the + * Handler is empty. + * + * @param what the Message.what value to be removed + */ + private void removeEqualMessages(int what) { + removeEqualMessages(what, null /* obj */); } - private void sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) { - super.sendMessageDelayed(what, token, arg2, data, timeout); + /** + * Removes all messages matching the given parameters, and attempts to release mWakeLock if the + * Handler is empty. + * + * @param what the Message.what value to be removed + * @param obj the Message.obj to to be removed, or null if all messages matching Message.what + * should be removed + */ + private void removeEqualMessages(int what, @Nullable Object obj) { + final Handler handler = getHandler(); + if (handler != null) { + handler.removeEqualMessages(what, obj); + } + + maybeReleaseWakeLock(); + } + + private WakeupMessage createScheduledAlarm( + @NonNull String cmdName, Message delayedMessage, long delay) { + // WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable + // at the scheduled time. dispatchMessage() immediately executes and there may be queued + // events that resolve the scheduled alarm pending in the queue. So, use the Runnable to + // place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which + // guarantees the device will stay awake). + final WakeupMessage alarm = + mDeps.newWakeupMessage( + mVcnContext, + getHandler(), + cmdName, + () -> sendMessageAndAcquireWakeLock(delayedMessage)); + alarm.schedule(mDeps.getElapsedRealTime() + delay); + return alarm; + } + + private void setTeardownTimeoutAlarm() { + // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In + // either case, there is nothing to cancel. + if (mTeardownTimeoutAlarm != null) { + Slog.wtf(TAG, "mTeardownTimeoutAlarm should be null before being set"); + } + + final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken); + mTeardownTimeoutAlarm = + createScheduledAlarm( + TEARDOWN_TIMEOUT_ALARM, + delayedMessage, + TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + } + + private void cancelTeardownTimeoutAlarm() { + if (mTeardownTimeoutAlarm != null) { + mTeardownTimeoutAlarm.cancel(); + mTeardownTimeoutAlarm = null; + } + + // Cancel any existing teardown timeouts + removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED); + } + + private void setDisconnectRequestAlarm() { + // Only schedule a NEW alarm if none is already set. + if (mDisconnectRequestAlarm != null) { + return; + } + + final Message delayedMessage = + obtainMessage( + EVENT_DISCONNECT_REQUESTED, + TOKEN_ALL, + 0 /* arg2 */, + new EventDisconnectRequestedInfo( + DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + mDisconnectRequestAlarm = + createScheduledAlarm( + DISCONNECT_REQUEST_ALARM, + delayedMessage, + TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS)); + } + + private void cancelDisconnectRequestAlarm() { + if (mDisconnectRequestAlarm != null) { + mDisconnectRequestAlarm.cancel(); + mDisconnectRequestAlarm = null; + } + + // Cancel any existing disconnect due to previous loss of underlying network + removeEqualMessages( + EVENT_DISCONNECT_REQUESTED, + new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)); + } + + private void setRetryTimeoutAlarm(long delay) { + // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In + // either case, there is nothing to cancel. + if (mRetryTimeoutAlarm != null) { + Slog.wtf(TAG, "mRetryTimeoutAlarm should be null before being set"); + } + + final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken); + mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay); + } + + private void cancelRetryTimeoutAlarm() { + if (mRetryTimeoutAlarm != null) { + mRetryTimeoutAlarm.cancel(); + mRetryTimeoutAlarm = null; + } + + removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED); } private void sessionLost(int token, @Nullable Exception exception) { - sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); + sendMessageAndAcquireWakeLock( + EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); } private void sessionClosed(int token, @Nullable Exception exception) { // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the // Disconnecting state. sessionLost(token, exception); - sendMessage(EVENT_SESSION_CLOSED, token); + sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token); } private void childTransformCreated( int token, @NonNull IpSecTransform transform, int direction) { - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_TRANSFORM_CREATED, token, new EventTransformCreatedInfo(direction, transform)); } private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) { - sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig)); + sendMessageAndAcquireWakeLock( + EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig)); } private abstract class BaseState extends State { @@ -671,7 +931,7 @@ public class VcnGatewayConnection extends StateMachine { enterState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo( @@ -682,22 +942,47 @@ public class VcnGatewayConnection extends StateMachine { protected void enterState() throws Exception {} /** + * Returns whether the given token is valid. + * + * <p>By default, States consider any and all token to be 'valid'. + * + * <p>States should override this method if they want to restrict message handling to + * specific tokens. + */ + protected boolean isValidToken(int token) { + return true; + } + + /** * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng * builds. + * + * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once + * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST + * ensure that mWakeLock is correctly released. */ @Override - public boolean processMessage(Message msg) { + public final boolean processMessage(Message msg) { + final int token = msg.arg1; + if (!isValidToken(token)) { + Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what); + return HANDLED; + } + try { processStateMsg(msg); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo( DISCONNECT_REASON_INTERNAL_ERROR + e.toString())); } + // Attempt to release the WakeLock - only possible if the Handler queue is empty + maybeReleaseWakeLock(); + return HANDLED; } @@ -709,7 +994,7 @@ public class VcnGatewayConnection extends StateMachine { exitState(); } catch (Exception e) { Slog.wtf(TAG, "Uncaught exception", e); - sendMessage( + sendMessageAndAcquireWakeLock( EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo( @@ -813,24 +1098,7 @@ public class VcnGatewayConnection extends StateMachine { } private abstract class ActiveBaseState extends BaseState { - /** - * Handles all incoming messages, discarding messages for previous networks. - * - * <p>States that handle mobility events may need to override this method to receive - * messages for all underlying networks. - */ @Override - public boolean processMessage(Message msg) { - final int token = msg.arg1; - // Only process if a valid token is presented. - if (isValidToken(token)) { - return super.processMessage(msg); - } - - Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what); - return HANDLED; - } - protected boolean isValidToken(int token) { return (token == TOKEN_ALL || token == mCurrentToken); } @@ -861,7 +1129,7 @@ public class VcnGatewayConnection extends StateMachine { protected void enterState() throws Exception { if (mIkeSession == null) { Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state."); - sendMessage(EVENT_SESSION_CLOSED, mCurrentToken); + sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken); return; } @@ -873,10 +1141,9 @@ public class VcnGatewayConnection extends StateMachine { } mIkeSession.close(); - sendMessageDelayed( - EVENT_TEARDOWN_TIMEOUT_EXPIRED, - mCurrentToken, - TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + + // Safe to blindly set up, as it is cancelled and cleared on exiting this state + setTeardownTimeoutAlarm(); } @Override @@ -927,6 +1194,8 @@ public class VcnGatewayConnection extends StateMachine { @Override protected void exitState() throws Exception { mSkipRetryTimeout = false; + + cancelTeardownTimeoutAlarm(); } } @@ -1117,6 +1386,18 @@ public class VcnGatewayConnection extends StateMachine { class ConnectedState extends ConnectedStateBase { @Override protected void enterState() throws Exception { + if (mTunnelIface == null) { + try { + // Requires a real Network object in order to be created; doing this any earlier + // means not having a real Network object, or picking an incorrect Network. + mTunnelIface = + mIpSecManager.createIpSecTunnelInterface( + DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network); + } catch (IOException | ResourceUnavailableException e) { + teardownAsynchronously(); + } + } + // Successful connection, clear failed attempt counter mFailedAttempts = 0; } @@ -1216,8 +1497,8 @@ public class VcnGatewayConnection extends StateMachine { Slog.wtf(TAG, "Underlying network was null in retry state"); transitionTo(mDisconnectedState); } else { - sendMessageDelayed( - EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs()); + // Safe to blindly set up, as it is cancelled and cleared on exiting this state + setRetryTimeoutAlarm(getNextRetryIntervalsMs()); } } @@ -1230,8 +1511,6 @@ public class VcnGatewayConnection extends StateMachine { // If new underlying is null, all networks were lost; go back to disconnected. if (mUnderlying == null) { - removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); - transitionTo(mDisconnectedState); return; } else if (oldUnderlying != null @@ -1242,8 +1521,6 @@ public class VcnGatewayConnection extends StateMachine { // Fallthrough case EVENT_RETRY_TIMEOUT_EXPIRED: - removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED); - transitionTo(mConnectingState); break; case EVENT_DISCONNECT_REQUESTED: @@ -1255,6 +1532,11 @@ public class VcnGatewayConnection extends StateMachine { } } + @Override + public void exitState() { + cancelRetryTimeoutAlarm(); + } + private long getNextRetryIntervalsMs() { final int retryDelayIndex = mFailedAttempts - 1; final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs(); @@ -1434,6 +1716,11 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) + void setTunnelInterface(IpSecTunnelInterface tunnelIface) { + mTunnelIface = tunnelIface; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() { return mUnderlyingNetworkTrackerCallback; } @@ -1522,6 +1809,26 @@ public class VcnGatewayConnection extends StateMachine { ikeSessionCallback, childSessionCallback); } + + /** Builds a new WakeLock. */ + public VcnWakeLock newWakeLock( + @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) { + return new VcnWakeLock(context, wakeLockFlag, wakeLockTag); + } + + /** Builds a new WakeupMessage. */ + public WakeupMessage newWakeupMessage( + @NonNull VcnContext vcnContext, + @NonNull Handler handler, + @NonNull String tag, + @NonNull Runnable runnable) { + return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable); + } + + /** Gets the elapsed real time since boot, in millis. */ + public long getElapsedRealTime() { + return SystemClock.elapsedRealtime(); + } } /** @@ -1601,4 +1908,34 @@ public class VcnGatewayConnection extends StateMachine { mImpl.setNetwork(network); } } + + /** Proxy Implementation of WakeLock, used for testing. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class VcnWakeLock { + private final WakeLock mImpl; + + public VcnWakeLock(@NonNull Context context, int flags, @NonNull String tag) { + final PowerManager powerManager = context.getSystemService(PowerManager.class); + mImpl = powerManager.newWakeLock(flags, tag); + mImpl.setReferenceCounted(false /* isReferenceCounted */); + } + + /** + * Acquire this WakeLock. + * + * <p>Synchronize this action to minimize locking around WakeLock use. + */ + public synchronized void acquire() { + mImpl.acquire(); + } + + /** + * Release this Wakelock. + * + * <p>Synchronize this action to minimize locking around WakeLock use. + */ + public synchronized void release() { + mImpl.release(); + } + } } diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java index 39687231c249..685dce4683d7 100644 --- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java +++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java @@ -21,16 +21,14 @@ import android.hardware.input.InputManager; import android.os.CombinedVibrationEffect; import android.os.Handler; import android.os.VibrationAttributes; -import android.os.VibrationEffect; -import android.os.Vibrator; +import android.os.VibratorManager; import android.util.SparseArray; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; -/** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */ -// TODO(b/159207608): Make this package-private once vibrator services are moved to this package -public final class InputDeviceDelegate implements InputManager.InputDeviceListener { +/** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */ +final class InputDeviceDelegate implements InputManager.InputDeviceListener { private static final String TAG = "InputDeviceDelegate"; private final Object mLock = new Object(); @@ -38,7 +36,7 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen private final InputManager mInputManager; @GuardedBy("mLock") - private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>(); + private final SparseArray<VibratorManager> mInputDeviceVibrators = new SparseArray<>(); /** * Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link @@ -47,7 +45,7 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen @GuardedBy("mLock") private boolean mShouldVibrateInputDevices; - public InputDeviceDelegate(Context context, Handler handler) { + InputDeviceDelegate(Context context, Handler handler) { mHandler = handler; mInputManager = context.getSystemService(InputManager.class); } @@ -81,32 +79,22 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen } /** - * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect. + * Vibrate all {@link InputDevice} with vibrators using given effect. * * @return {@link #isAvailable()} */ public boolean vibrateIfAvailable(int uid, String opPkg, CombinedVibrationEffect effect, String reason, VibrationAttributes attrs) { synchronized (mLock) { - // TODO(b/159207608): Pass on the combined vibration once InputManager is merged - if (effect instanceof CombinedVibrationEffect.Mono) { - VibrationEffect e = ((CombinedVibrationEffect.Mono) effect).getEffect(); - if (e instanceof VibrationEffect.Prebaked) { - VibrationEffect fallback = ((VibrationEffect.Prebaked) e).getFallbackEffect(); - if (fallback != null) { - e = fallback; - } - } - for (int i = 0; i < mInputDeviceVibrators.size(); i++) { - mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, e, reason, attrs); - } + for (int i = 0; i < mInputDeviceVibrators.size(); i++) { + mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs); } return mInputDeviceVibrators.size() > 0; } } /** - * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available. + * Cancel vibration on all {@link InputDevice} with vibrators. * * @return {@link #isAvailable()} */ @@ -147,9 +135,9 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen if (device == null) { continue; } - Vibrator vibrator = device.getVibrator(); - if (vibrator.hasVibrator()) { - mInputDeviceVibrators.put(device.getId(), vibrator); + VibratorManager vibratorManager = device.getVibratorManager(); + if (vibratorManager.getVibratorIds().length > 0) { + mInputDeviceVibrators.put(device.getId(), vibratorManager); } } } else { @@ -171,9 +159,9 @@ public final class InputDeviceDelegate implements InputManager.InputDeviceListen mInputDeviceVibrators.remove(deviceId); return; } - Vibrator vibrator = device.getVibrator(); - if (vibrator.hasVibrator()) { - mInputDeviceVibrators.put(deviceId, vibrator); + VibratorManager vibratorManager = device.getVibratorManager(); + if (vibratorManager.getVibratorIds().length > 0) { + mInputDeviceVibrators.put(device.getId(), vibratorManager); } else { mInputDeviceVibrators.remove(deviceId); } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index e0f5408a1537..607da3ce6fe2 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -25,25 +25,16 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.util.proto.ProtoOutputStream; -import com.android.server.ComposedProto; -import com.android.server.OneShotProto; -import com.android.server.PrebakedProto; -import com.android.server.VibrationAttributesProto; -import com.android.server.VibrationEffectProto; -import com.android.server.VibrationProto; -import com.android.server.WaveformProto; - import java.text.SimpleDateFormat; import java.util.Date; /** Represents a vibration request to the vibrator service. */ -// TODO(b/159207608): Make this package-private once vibrator services are moved to this package -public class Vibration { +final class Vibration { private static final String TAG = "Vibration"; private static final SimpleDateFormat DEBUG_DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - public enum Status { + enum Status { RUNNING, FINISHED, FORWARDED_TO_INPUT_DEVICES, @@ -91,7 +82,7 @@ public class Vibration { private long mEndTimeDebug; private Status mStatus; - public Vibration(IBinder token, int id, CombinedVibrationEffect effect, + Vibration(IBinder token, int id, CombinedVibrationEffect effect, VibrationAttributes attrs, int uid, String opPkg, String reason) { this.token = token; this.mEffect = effect; @@ -157,7 +148,7 @@ public class Vibration { } /** Debug information about vibrations. */ - public static final class DebugInfo { + static final class DebugInfo { private final long mStartTimeDebug; private final long mEndTimeDebug; private final CombinedVibrationEffect mEffect; @@ -169,7 +160,7 @@ public class Vibration { private final String mReason; private final Status mStatus; - public DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect, + DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect, CombinedVibrationEffect originalEffect, float scale, VibrationAttributes attrs, int uid, String opPkg, String reason, Status status) { mStartTimeDebug = startTimeDebug; @@ -235,21 +226,49 @@ public class Vibration { } private void dumpEffect( - ProtoOutputStream proto, long fieldId, CombinedVibrationEffect combinedEffect) { - VibrationEffect effect; - // TODO(b/177805090): add proper support for dumping combined effects to proto - if (combinedEffect instanceof CombinedVibrationEffect.Mono) { - effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect(); - } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) { - effect = ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects().valueAt(0); - } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) { - dumpEffect(proto, fieldId, - ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects().get(0)); - return; - } else { - // Unknown combined effect, skip dump. - return; + ProtoOutputStream proto, long fieldId, CombinedVibrationEffect effect) { + dumpEffect(proto, fieldId, + (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential() + .addNext(effect) + .combine()); + } + + private void dumpEffect( + ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Sequential effect) { + final long token = proto.start(fieldId); + for (int i = 0; i < effect.getEffects().size(); i++) { + CombinedVibrationEffect nestedEffect = effect.getEffects().get(i); + if (nestedEffect instanceof CombinedVibrationEffect.Mono) { + dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS, + (CombinedVibrationEffect.Mono) nestedEffect); + } else if (nestedEffect instanceof CombinedVibrationEffect.Stereo) { + dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS, + (CombinedVibrationEffect.Stereo) nestedEffect); + } + proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i)); + } + proto.end(token); + } + + private void dumpEffect( + ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Mono effect) { + final long token = proto.start(fieldId); + dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect()); + proto.end(token); + } + + private void dumpEffect( + ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Stereo effect) { + final long token = proto.start(fieldId); + for (int i = 0; i < effect.getEffects().size(); i++) { + proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i)); + dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i)); } + proto.end(token); + } + + 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); diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index 0fa4fe16e1ba..10393f682279 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -29,8 +29,7 @@ import java.util.List; import java.util.Objects; /** Controls vibration scaling. */ -// TODO(b/159207608): Make this package-private once vibrator services are moved to this package -public final class VibrationScaler { +final class VibrationScaler { private static final String TAG = "VibrationScaler"; // Scale levels. Each level, except MUTE, is defined as the delta between the current setting @@ -56,7 +55,7 @@ public final class VibrationScaler { private final VibrationSettings mSettingsController; private final int mDefaultVibrationAmplitude; - public VibrationScaler(Context context, VibrationSettings settingsController) { + VibrationScaler(Context context, VibrationSettings settingsController) { mSettingsController = settingsController; mDefaultVibrationAmplitude = context.getResources().getInteger( com.android.internal.R.integer.config_defaultVibrationAmplitude); diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 8910bdfa1c05..334129d6bde9 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -39,20 +39,18 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; -import com.android.server.VibratorServiceDumpProto; import java.util.ArrayList; import java.util.List; /** Controls all the system settings related to vibration. */ -// TODO(b/159207608): Make this package-private once vibrator services are moved to this package -public final class VibrationSettings { +final class VibrationSettings { private static final String TAG = "VibrationSettings"; private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = {0, 30, 100, 30}; /** Listener for changes on vibration settings. */ - public interface OnVibratorSettingsChanged { + interface OnVibratorSettingsChanged { /** Callback triggered when any of the vibrator settings change. */ void onChange(); } @@ -86,7 +84,7 @@ public final class VibrationSettings { @GuardedBy("mLock") private boolean mLowPowerMode; - public VibrationSettings(Context context, Handler handler) { + VibrationSettings(Context context, Handler handler) { mContext = context; mVibrator = context.getSystemService(Vibrator.class); mAudioManager = context.getSystemService(AudioManager.class); @@ -345,17 +343,17 @@ public final class VibrationSettings { /** Write current settings into given {@link ProtoOutputStream}. */ public void dumpProto(ProtoOutputStream proto) { synchronized (mLock) { - proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, + proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, mHapticFeedbackIntensity); - proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, + proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, mVibrator.getDefaultHapticFeedbackIntensity()); - proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY, + proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY, mNotificationIntensity); - proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, + proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, mVibrator.getDefaultNotificationVibrationIntensity()); - proto.write(VibratorServiceDumpProto.RING_INTENSITY, + proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY, mRingIntensity); - proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY, + proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY, mVibrator.getDefaultRingVibrationIntensity()); } } diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 04dac7c2b198..389326769096 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -42,8 +42,7 @@ import java.util.List; import java.util.PriorityQueue; /** Plays a {@link Vibration} in dedicated thread. */ -// TODO(b/159207608): Make this package-private once vibrator services are moved to this package -public final class VibrationThread extends Thread implements IBinder.DeathRecipient { +final class VibrationThread extends Thread implements IBinder.DeathRecipient { private static final String TAG = "VibrationThread"; private static final boolean DEBUG = false; @@ -54,7 +53,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi private static final long CALLBACKS_EXTRA_TIMEOUT = 100; /** Callbacks for playing a {@link Vibration}. */ - public interface VibrationCallbacks { + interface VibrationCallbacks { /** * Callback triggered before starting a synchronized vibration step. This will be called @@ -92,7 +91,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi @GuardedBy("mLock") private boolean mForceStop; - public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators, + VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, VibrationCallbacks callbacks) { mVibration = vib; @@ -169,7 +168,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi } } - public Vibration getVibration() { + Vibration getVibration() { return mVibration; } diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 9dcf12caa55b..95f6059c482e 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -32,8 +32,7 @@ import com.android.internal.annotations.VisibleForTesting; import libcore.util.NativeAllocationRegistry; /** Controls a single vibrator. */ -// TODO(b/159207608): Make this package-private once vibrator services are moved to this package -public final class VibratorController { +final class VibratorController { private static final String TAG = "VibratorController"; private final Object mLock = new Object(); @@ -99,12 +98,12 @@ public final class VibratorController { static native void vibratorAlwaysOnDisable(long nativePtr, long id); - public VibratorController(int vibratorId, OnVibrationCompleteListener listener) { + VibratorController(int vibratorId, OnVibrationCompleteListener listener) { this(vibratorId, listener, new NativeWrapper()); } @VisibleForTesting - public VibratorController(int vibratorId, OnVibrationCompleteListener listener, + VibratorController(int vibratorId, OnVibrationCompleteListener listener, NativeWrapper nativeWrapper) { mNativeWrapper = nativeWrapper; mNativeWrapper.init(vibratorId, listener); @@ -142,11 +141,6 @@ public final class VibratorController { } } - @VisibleForTesting - public NativeWrapper getNativeWrapper() { - return mNativeWrapper; - } - /** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */ public VibratorInfo getVibratorInfo() { return mVibratorInfo; diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index d264f8570cf2..175085475b6c 100644 --- a/services/core/java/com/android/server/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.vibrator; import android.annotation.NonNull; import android.annotation.Nullable; @@ -56,12 +56,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; -import com.android.server.vibrator.InputDeviceDelegate; -import com.android.server.vibrator.Vibration; -import com.android.server.vibrator.VibrationScaler; -import com.android.server.vibrator.VibrationSettings; -import com.android.server.vibrator.VibrationThread; -import com.android.server.vibrator.VibratorController; +import com.android.server.LocalServices; +import com.android.server.SystemService; import libcore.util.NativeAllocationRegistry; @@ -356,10 +352,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return; } - VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock, - mBatteryStatsService, mVibrationCallbacks); - - ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread); + ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib); if (ignoreStatus != null) { endVibrationLocked(vib, ignoreStatus); return; @@ -370,7 +363,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (mCurrentVibration != null) { mCurrentVibration.cancel(); } - Vibration.Status status = startVibrationLocked(vibThread); + Vibration.Status status = startVibrationLocked(vib); if (status != Vibration.Status.RUNNING) { endVibrationLocked(vib, status); } @@ -495,19 +488,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @GuardedBy("mLock") - private Vibration.Status startVibrationLocked(VibrationThread vibThread) { + private Vibration.Status startVibrationLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - Vibration vib = vibThread.getVibration(); vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage())); - boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); - if (inputDevicesAvailable) { return Vibration.Status.FORWARDED_TO_INPUT_DEVICES; } + VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock, + mBatteryStatsService, mVibrationCallbacks); + if (mCurrentVibration == null) { return startVibrationThreadLocked(vibThread); } @@ -599,8 +592,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @GuardedBy("mLock") @Nullable - private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) { - if (vibThread.getVibration().isRepeating()) { + private Vibration.Status shouldIgnoreVibrationForCurrentLocked(Vibration vibration) { + if (vibration.isRepeating()) { // Repeating vibrations always take precedence. return null; } @@ -967,7 +960,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /** Listener for synced vibration completion callbacks from native. */ @VisibleForTesting - public interface OnSyncedVibrationCompleteListener { + interface OnSyncedVibrationCompleteListener { /** Callback triggered when synced vibration is complete. */ void onComplete(long vibrationId); @@ -1167,6 +1160,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + pw.println(); pw.println(" Previous external vibrations:"); for (Vibration.DebugInfo info : mPreviousExternalVibrations) { pw.println(" " + info); @@ -1181,46 +1175,48 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibrationSettings.dumpProto(proto); if (mCurrentVibration != null) { mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto, - VibratorServiceDumpProto.CURRENT_VIBRATION); + VibratorManagerServiceDumpProto.CURRENT_VIBRATION); } if (mCurrentExternalVibration != null) { mCurrentExternalVibration.getDebugInfo().dumpProto(proto, - VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION); + VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION); } boolean isVibrating = false; boolean isUnderExternalControl = false; for (int i = 0; i < mVibrators.size(); i++) { + proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i)); isVibrating |= mVibrators.valueAt(i).isVibrating(); isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl(); } - proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating); - proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL, + proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating); + proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL, isUnderExternalControl); - for (Vibration.DebugInfo info : mPreviousVibrations.get( - VibrationAttributes.USAGE_RINGTONE)) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousVibrations.get( - VibrationAttributes.USAGE_NOTIFICATION)) { - info.dumpProto(proto, - VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousVibrations.get( - VibrationAttributes.USAGE_ALARM)) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousVibrations.get( - VibrationAttributes.USAGE_UNKNOWN)) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS); + for (int i = 0; i < mPreviousVibrations.size(); i++) { + long fieldId; + switch (mPreviousVibrations.keyAt(i)) { + case VibrationAttributes.USAGE_RINGTONE: + fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS; + break; + case VibrationAttributes.USAGE_NOTIFICATION: + fieldId = VibratorManagerServiceDumpProto + .PREVIOUS_NOTIFICATION_VIBRATIONS; + break; + case VibrationAttributes.USAGE_ALARM: + fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS; + break; + default: + fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS; + } + for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) { + info.dumpProto(proto, fieldId); + } } for (Vibration.DebugInfo info : mPreviousExternalVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS); + info.dumpProto(proto, + VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS); } } proto.flush(); @@ -1300,7 +1296,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { cancelingVibration.join(); } catch (InterruptedException e) { Slog.w("Interrupted while waiting for vibration to finish before starting " - + "external control", e); + + "external control", e); } } if (DEBUG) { diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 771b712cf480..c63a0f093dea 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -134,10 +134,10 @@ class ActivityClientController extends IActivityClientController.Stub { } @Override - public void activityResumed(IBinder token) { + public void activityResumed(IBinder token, boolean handleSplashScreenExit) { final long origId = Binder.clearCallingIdentity(); synchronized (mGlobalLock) { - ActivityRecord.activityResumedLocked(token); + ActivityRecord.activityResumedLocked(token, handleSplashScreenExit); } Binder.restoreCallingIdentity(origId); } @@ -692,6 +692,18 @@ class ActivityClientController extends IActivityClientController.Stub { } /** + * Splash screen view is attached to activity. + */ + @Override + public void splashScreenAttached(IBinder token) { + final long origId = Binder.clearCallingIdentity(); + synchronized (mGlobalLock) { + ActivityRecord.splashScreenAttachedLocked(token); + } + Binder.restoreCallingIdentity(origId); + } + + /** * Checks the state of the system and the activity associated with the given {@param token} to * verify that picture-in-picture is supported for that activity. * diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 68a2c5d5233c..9bf6df41a93b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -42,6 +42,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO; +import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; @@ -243,6 +245,7 @@ import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StartActivityItem; import android.app.servertransaction.StopActivityItem; import android.app.servertransaction.TopResumedActivityChangeItem; +import android.app.servertransaction.TransferSplashScreenViewStateItem; import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.Intent; @@ -300,6 +303,7 @@ import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionOldType; import android.view.animation.Animation; import android.window.IRemoteTransition; +import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -669,6 +673,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean startingDisplayed; boolean startingMoved; + boolean mHandleExitSplashScreen; + @TransferSplashScreenState int mTransferringSplashScreenState = + TRANSFER_SPLASH_SCREEN_IDLE; + + // Idle, can be triggered to do transfer if needed. + static final int TRANSFER_SPLASH_SCREEN_IDLE = 0; + // requesting a copy from shell. + static final int TRANSFER_SPLASH_SCREEN_COPYING = 1; + // attach the splash screen view to activity. + static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2; + // client has taken over splash screen view. + static final int TRANSFER_SPLASH_SCREEN_FINISH = 3; + + @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = { + TRANSFER_SPLASH_SCREEN_IDLE, + TRANSFER_SPLASH_SCREEN_COPYING, + TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT, + TRANSFER_SPLASH_SCREEN_FINISH, + }) + @interface TransferSplashScreenState {} + + // How long we wait until giving up transfer splash screen. + private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000; + // TODO: Have a WindowContainer state for tracking exiting/deferred removal. boolean mIsExiting; // Force an app transition to be ran in the case the visibility of the app did not change. @@ -1727,6 +1755,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (_createTime > 0) { createTime = _createTime; } + mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName); } /** @@ -1807,7 +1836,85 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return hasProcess() && app.hasThread(); } - boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo, + /** + * Evaluate the theme for a starting window. + * @param originalTheme The original theme which read from activity or application. + * @param replaceTheme The replace theme which requested from starter. + * @return Resolved theme. + */ + private int evaluateStartingWindowTheme(String pkg, int originalTheme, int replaceTheme) { + // Skip if the package doesn't want a starting window. + if (!validateStartingWindowTheme(pkg, originalTheme)) { + return 0; + } + int selectedTheme = originalTheme; + if (replaceTheme != 0 && validateStartingWindowTheme(pkg, replaceTheme)) { + // allow to replace theme + selectedTheme = replaceTheme; + } + return selectedTheme; + } + + private boolean validateStartingWindowTheme(String pkg, int theme) { + // If this is a translucent window, then don't show a starting window -- the current + // effect (a full-screen opaque starting window that fades away to the real contents + // when it is ready) does not work for this. + ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme); + if (theme != 0) { + AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, + com.android.internal.R.styleable.Window, + mWmService.mCurrentUserId); + if (ent == null) { + // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't + // see that. + return false; + } + final boolean windowIsTranslucent = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowIsTranslucent, false); + final boolean windowIsFloating = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowIsFloating, false); + final boolean windowShowWallpaper = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowShowWallpaper, false); + final boolean windowDisableStarting = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowDisablePreview, false); + ProtoLog.v(WM_DEBUG_STARTING_WINDOW, + "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s", + windowIsTranslucent, windowIsFloating, windowShowWallpaper, + windowDisableStarting); + if (windowIsTranslucent || windowIsFloating || windowDisableStarting) { + return false; + } + if (windowShowWallpaper + && getDisplayContent().mWallpaperController.getWallpaperTarget() != null) { + return false; + } + } + return true; + } + + private void applyStartingWindowTheme(String pkg, int theme) { + if (theme != 0) { + AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, + com.android.internal.R.styleable.Window, + mWmService.mCurrentUserId); + if (ent == null) { + return; + } + final boolean windowShowWallpaper = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowShowWallpaper, false); + if (windowShowWallpaper && getDisplayContent().mWallpaperController + .getWallpaperTarget() == null) { + // If this theme is requesting a wallpaper, and the wallpaper + // is not currently visible, then this effectively serves as + // an opaque window and our starting window transition animation + // can still work. We just need to make sure the starting window + // is also showing the wallpaper. + windowFlags |= FLAG_SHOW_WALLPAPER; + } + } + } + + boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated) { @@ -1850,49 +1957,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return createSnapshot(snapshot, typeParameter); } - // If this is a translucent window, then don't show a starting window -- the current - // effect (a full-screen opaque starting window that fades away to the real contents - // when it is ready) does not work for this. - ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme); - if (theme != 0) { - AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, - com.android.internal.R.styleable.Window, - mWmService.mCurrentUserId); - if (ent == null) { - // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't - // see that. - return false; - } - final boolean windowIsTranslucent = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsTranslucent, false); - final boolean windowIsFloating = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsFloating, false); - final boolean windowShowWallpaper = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowShowWallpaper, false); - final boolean windowDisableStarting = ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowDisablePreview, false); - ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s", - windowIsTranslucent, windowIsFloating, windowShowWallpaper); - if (windowIsTranslucent) { - return false; - } - if (windowIsFloating || windowDisableStarting) { - return false; - } - if (windowShowWallpaper) { - if (getDisplayContent().mWallpaperController - .getWallpaperTarget() == null) { - // If this theme is requesting a wallpaper, and the wallpaper - // is not currently visible, then this effectively serves as - // an opaque window and our starting window transition animation - // can still work. We just need to make sure the starting window - // is also showing the wallpaper. - windowFlags |= FLAG_SHOW_WALLPAPER; - } else { - return false; - } - } + // Original theme can be 0 if developer doesn't request any theme. So if resolved theme is 0 + // but original theme is not 0, means this package doesn't want a starting window. + if (resolvedTheme == 0 && theme != 0) { + return false; } + applyStartingWindowTheme(pkg, resolvedTheme); if (transferStartingWindow(transferFrom)) { return true; @@ -1906,7 +1976,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData"); mStartingData = new SplashScreenStartingData(mWmService, pkg, - theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, + resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, getMergedOverrideConfiguration(), typeParameter); scheduleAddStartingWindow(); return true; @@ -2031,7 +2101,118 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return snapshot.getRotation() == targetRotation; } + /** + * See {@link SplashScreen#setOnExitAnimationListener}. + */ + void setCustomizeSplashScreenExitAnimation(boolean enable) { + if (mHandleExitSplashScreen == enable) { + return; + } + mHandleExitSplashScreen = enable; + } + + private final Runnable mTransferSplashScreenTimeoutRunnable = new Runnable() { + @Override + public void run() { + synchronized (mAtmService.mGlobalLock) { + Slog.w(TAG, "Activity transferring splash screen timeout for " + + ActivityRecord.this + " state " + mTransferringSplashScreenState); + if (isTransferringSplashScreen()) { + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; + // TODO show default exit splash screen animation + removeStartingWindow(); + } + } + } + }; + + private void scheduleTransferSplashScreenTimeout() { + mAtmService.mH.postDelayed(mTransferSplashScreenTimeoutRunnable, + TRANSFER_SPLASH_SCREEN_TIMEOUT); + } + + private void removeTransferSplashScreenTimeout() { + mAtmService.mH.removeCallbacks(mTransferSplashScreenTimeoutRunnable); + } + + private boolean transferSplashScreenIfNeeded() { + if (!mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null + || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) { + return false; + } + if (isTransferringSplashScreen()) { + return true; + } + requestCopySplashScreen(); + return isTransferringSplashScreen(); + } + + private boolean isTransferringSplashScreen() { + return mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT + || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING; + } + + private void requestCopySplashScreen() { + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING; + if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) { + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; + removeStartingWindow(); + } + scheduleTransferSplashScreenTimeout(); + } + + /** + * Receive the splash screen data from shell, sending to client. + * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy. + */ + void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) { + removeTransferSplashScreenTimeout(); + // unable to copy from shell, maybe it's not a splash screen. or something went wrong. + // either way, abort and reset the sequence. + if (parcelable == null + || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) { + if (parcelable != null) { + parcelable.clearIfNeeded(); + } + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; + removeStartingWindow(); + return; + } + // schedule attach splashScreen to client + try { + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT; + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable)); + scheduleTransferSplashScreenTimeout(); + } catch (Exception e) { + Slog.w(TAG, "onCopySplashScreenComplete fail: " + this); + parcelable.clearIfNeeded(); + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; + } + } + + private void onSplashScreenAttachComplete() { + removeTransferSplashScreenTimeout(); + // Client has draw the splash screen, so we can remove the starting window. + if (mStartingWindow != null) { + mStartingWindow.hide(false, false); + } + try { + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null)); + } catch (Exception e) { + Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this); + } + // no matter what, remove the starting window. + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; + removeStartingWindow(); + } + void removeStartingWindow() { + if (transferSplashScreenIfNeeded()) { + return; + } + mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; if (mStartingWindow == null) { if (mStartingData != null) { // Starting window has not been added yet, but it is scheduled to be added. @@ -5092,7 +5273,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - static void activityResumedLocked(IBinder token) { + static void activityResumedLocked(IBinder token, boolean handleSplashScreenExit) { final ActivityRecord r = ActivityRecord.forTokenLocked(token); ProtoLog.i(WM_DEBUG_STATES, "Resumed activity; dropping state of: %s", r); if (r == null) { @@ -5100,12 +5281,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // been removed (e.g. destroy timeout), so the token could be null. return; } + r.setCustomizeSplashScreenExitAnimation(handleSplashScreenExit); r.setSavedState(null /* savedState */); r.mDisplayContent.handleActivitySizeCompatModeIfNeeded(r); r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r); } + static void splashScreenAttachedLocked(IBinder token) { + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + if (r == null) { + Slog.w(TAG, "splashScreenTransferredLocked cannot find activity"); + return; + } + r.onSplashScreenAttachComplete(); + } + /** * Once we know that we have asked an application to put an activity in the resumed state * (either by launching it or explicitly telling it), this function updates the rest of our @@ -5187,6 +5378,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + mDisplayContent.handleActivitySizeCompatModeIfNeeded(this); mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); } @@ -5931,7 +6123,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pendingVoiceInteractionStart = false; } - void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) { + void showStartingWindow(boolean taskSwitch) { + showStartingWindow(null /* prev */, false /* newTask */, taskSwitch, + 0 /* splashScreenTheme */); + } + + void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch, + int splashScreenTheme) { if (mTaskOverlay) { // We don't show starting window for overlay activities. return; @@ -5944,7 +6142,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final CompatibilityInfo compatInfo = mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo); - final boolean shown = addStartingWindow(packageName, theme, + + final int resolvedTheme = evaluateStartingWindowTheme(packageName, theme, + splashScreenTheme); + final boolean shown = addStartingWindow(packageName, resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(), allowTaskSnapshot(), diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 3456e51d028a..37fda4ce217a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -772,11 +772,6 @@ class ActivityStarter { newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target)); - ActivityOptions options = mRequest.activityOptions.getOptions(mRequest.intent, - mRequest.activityInfo, - mService.getProcessController(mRequest.caller), - mSupervisor); - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_ACTIVITY_OPTIONS, options.toBundle()); heavy.updateIntentForHeavyWeightActivity(newIntent); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, mRequest.activityInfo.packageName); @@ -2002,8 +1997,7 @@ class ActivityStarter { if (mMovedToFront) { // We moved the task to front, use starting window to hide initial drawn delay. - targetTaskTop.showStartingWindow(null /* prev */, false /* newTask */, - true /* taskSwitch */); + targetTaskTop.showStartingWindow(true /* taskSwitch */); } else if (mDoResume) { // Make sure the root task and its belonging display are moved to topmost. mTargetRootTask.moveToFront("intentActivityFound"); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 7d2075cca84d..94379b1f230e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -574,4 +574,26 @@ public abstract class ActivityTaskManagerInternal { * @return Whether the package is the base of any locked task */ public abstract boolean isBaseOfLockedTask(String packageName); + + /** + * Create an interface to update configuration for an application. + */ + public abstract PackageConfigurationUpdater createPackageConfigurationUpdater(); + + /** + * An interface to update configuration for an application, and will persist override + * configuration for this package. + */ + public interface PackageConfigurationUpdater { + /** + * Sets the dark mode for the current application. This setting is persisted and will + * override the system configuration for this application. + */ + PackageConfigurationUpdater setNightMode(int nightMode); + + /** + * Commit changes. + */ + void commit() throws RemoteException; + } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f16a646d00a1..2e98c2cbc5c2 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -224,6 +224,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowManager; import android.window.IWindowOrganizerController; +import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; import android.window.WindowContainerTransaction; @@ -451,6 +452,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** The controller for all operations related to locktask. */ private LockTaskController mLockTaskController; private ActivityStartController mActivityStartController; + PackageConfigPersister mPackageConfigPersister; boolean mSuppressResizeConfigChanges; @@ -866,6 +868,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { setRecentTasks(new RecentTasks(this, mTaskSupervisor)); mVrController = new VrController(mGlobalLock); mKeyguardController = mTaskSupervisor.getKeyguardController(); + mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue); } public void onActivityManagerInternalAdded() { @@ -2027,8 +2030,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // We are reshowing a task, use a starting window to hide the initial draw delay // so the transition can start earlier. - topActivity.showStartingWindow(null /* prev */, false /* newTask */, - true /* taskSwitch */); + topActivity.showStartingWindow(true /* taskSwitch */); } } finally { Binder.restoreCallingIdentity(origId); @@ -3249,6 +3251,30 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** + * A splash screen view has copied, pass it to an activity. + * + * @param taskId Id of task to handle the material to reconstruct the view. + * @param parcelable Used to reconstruct the view, null means the surface is un-copyable. + * @hide + */ + @Override + public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable) + throws RemoteException { + mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS, + "copySplashScreenViewFinish()"); + synchronized (mGlobalLock) { + final Task task = mRootWindowContainer.anyTaskForId(taskId, + MATCH_ATTACHED_TASK_ONLY); + if (task != null) { + final ActivityRecord r = task.getTopWaitSplashScreenActivity(); + if (r != null) { + r.onCopySplashScreenFinish(parcelable); + } + } + } + } + + /** * Puts the given activity in picture in picture mode if possible. * * @return true if the activity is now in picture-in-picture mode, or false if it could not @@ -5433,6 +5459,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { mAppWarnings.onPackageUninstalled(name); mCompatModePackages.handlePackageUninstalledLocked(name); + mPackageConfigPersister.onPackageUninstall(name); } } @@ -6103,6 +6130,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void removeUser(int userId) { synchronized (mGlobalLock) { mRootWindowContainer.removeUser(userId); + mPackageConfigPersister.removeUser(userId); } } @@ -6200,6 +6228,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void loadRecentTasksForUser(int userId) { synchronized (mGlobalLock) { mRecentTasks.loadUserRecentsLocked(userId); + // TODO renaming the methods(?) + mPackageConfigPersister.loadUserPackages(userId); } } @@ -6308,5 +6338,54 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getLockTaskController().isBaseOfLockedTask(packageName); } } + + @Override + public PackageConfigurationUpdater createPackageConfigurationUpdater() { + synchronized (mGlobalLock) { + return new PackageConfigurationUpdaterImpl(Binder.getCallingPid()); + } + } + } + + final class PackageConfigurationUpdaterImpl implements + ActivityTaskManagerInternal.PackageConfigurationUpdater { + private int mPid; + private int mNightMode; + + PackageConfigurationUpdaterImpl(int pid) { + mPid = pid; + } + + @Override + public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) { + mNightMode = nightMode; + return this; + } + + @Override + public void commit() throws RemoteException { + if (mPid == 0) { + throw new RemoteException("Invalid process"); + } + synchronized (mGlobalLock) { + final WindowProcessController wpc = mProcessMap.getProcess(mPid); + if (wpc == null) { + Slog.w(TAG, "Override application configuration: cannot find application"); + return; + } + if (wpc.getNightMode() == mNightMode) { + return; + } + if (!wpc.setOverrideNightMode(mNightMode)) { + return; + } + wpc.updateNightModeForAllActivities(mNightMode); + mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this); + } + } + + int getNightMode() { + return mNightMode; + } } } diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index d90e88576909..efcaaa42ec13 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -534,6 +534,28 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return getActivityType() == ACTIVITY_TYPE_ASSISTANT; } + /** + * Overrides the night mode applied to this ConfigurationContainer. + * @return true if the nightMode has been changed. + */ + public boolean setOverrideNightMode(int nightMode) { + final int currentUiMode = mFullConfiguration.uiMode; + final int currentNightMode = getNightMode(); + final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK; + if (currentNightMode == validNightMode) { + return false; + } + mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); + mRequestsTmpConfig.uiMode = validNightMode + | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK); + onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); + return true; + } + + int getNightMode() { + return mFullConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK; + } + public boolean isActivityTypeDream() { return getActivityType() == ACTIVITY_TYPE_DREAM; } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index f075d850c20f..759b7fe054bc 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -429,6 +429,10 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) { if (mOrganizer == organizer) return; + if (mDisplayContent == null || !mDisplayContent.isTrusted()) { + throw new IllegalStateException( + "Don't organize or trigger events for unavailable or untrusted display."); + } IDisplayAreaOrganizer lastOrganizer = mOrganizer; // Update the new display area organizer before calling sendDisplayAreaVanished since it // could result in a new SurfaceControl getting created that would notify the old organizer @@ -500,6 +504,17 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { return false; } + @Override + void removeImmediately() { + setOrganizer(null); + super.removeImmediately(); + } + + @Override + DisplayArea getDisplayArea() { + return this; + } + /** * DisplayArea that contains WindowTokens, and orders them according to their type. */ @@ -580,11 +595,6 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { } } - @Override - DisplayArea getDisplayArea() { - return this; - } - /** * DisplayArea that can be dimmed. */ diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java index ed44876eb368..2beb3780633e 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java +++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java @@ -21,6 +21,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_RUNTIME_TASK_CONTAINER import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.DisplayArea.Type.ANY; +import android.annotation.Nullable; import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.IBinder; @@ -77,6 +78,11 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl mService.enforceTaskPermission(func); } + @Nullable + IDisplayAreaOrganizer getOrganizerByFeature(int featureId) { + return mOrganizersByFeatureIds.get(featureId); + } + @Override public ParceledListSlice<DisplayAreaAppearedInfo> registerOrganizer( IDisplayAreaOrganizer organizer, int feature) { @@ -100,10 +106,18 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl } final List<DisplayAreaAppearedInfo> displayAreaInfos = new ArrayList<>(); - mService.mRootWindowContainer.forAllDisplayAreas((da) -> { - if (da.mFeatureId != feature) return; - displayAreaInfos.add(organizeDisplayArea(organizer, da, - "DisplayAreaOrganizerController.registerOrganizer")); + mService.mRootWindowContainer.forAllDisplays(dc -> { + if (!dc.isTrusted()) { + ProtoLog.w(WM_DEBUG_WINDOW_ORGANIZER, + "Don't organize or trigger events for untrusted displayId=%d", + dc.getDisplayId()); + return; + } + dc.forAllDisplayAreas((da) -> { + if (da.mFeatureId != feature) return; + displayAreaInfos.add(organizeDisplayArea(organizer, da, + "DisplayAreaOrganizerController.registerOrganizer")); + }); }); mOrganizersByFeatureIds.put(feature, organizer); @@ -148,6 +162,10 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl throw new IllegalArgumentException("createTaskDisplayArea unknown displayId=" + displayId); } + if (!display.isTrusted()) { + throw new IllegalArgumentException("createTaskDisplayArea untrusted displayId=" + + displayId); + } // The parentFeatureId can be either a RootDisplayArea or a TaskDisplayArea. // Check if there is a RootDisplayArea with the given parentFeatureId. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 23eab98a671a..86968ed6d175 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -213,6 +213,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManagerPolicyConstants.PointerEventListener; +import android.window.IDisplayAreaOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -239,6 +240,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -662,12 +664,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private boolean mRemoved; - /** - * Non-null if the last size compatibility mode activity is using non-native screen - * configuration. The activity is not able to put in multi-window mode, so it exists only one - * per display. - */ - private ActivityRecord mLastCompatModeActivity; + /** Set of activities in foreground size compat mode. */ + private Set<ActivityRecord> mActiveSizeCompatActivities = new ArraySet<>(); // Used in updating the display size private Point mTmpDisplaySize = new Point(); @@ -1074,6 +1072,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Sets the display content for the children. onDisplayChanged(this); + updateDisplayAreaOrganizers(); mInputMonitor = new InputMonitor(mWmService, this); mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); @@ -2712,6 +2711,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** + * Checks for all non-organized {@link DisplayArea}s for if there is any existing organizer for + * their features. If so, registers them with the matched organizer. + */ + @VisibleForTesting + void updateDisplayAreaOrganizers() { + if (!isTrusted()) { + // No need to update for untrusted display. + return; + } + forAllDisplayAreas(displayArea -> { + if (displayArea.isOrganized()) { + return; + } + // Check if we have a registered organizer for the DA feature. + final IDisplayAreaOrganizer organizer = + mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController + .getOrganizerByFeature(displayArea.mFeatureId); + if (organizer != null) { + displayArea.setOrganizer(organizer); + } + }); + } + + /** * Returns true if the input point is within an app window. */ boolean pointWithinAppWindow(int x, int y) { @@ -5527,24 +5550,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Checks whether the given activity is in size compatibility mode and notifies the change. */ void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) { final Task organizedTask = r.getOrganizedTask(); - if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN - || organizedTask == null) { - // The callback is only interested in the foreground changes of fullscreen activity. + if (organizedTask == null) { + mActiveSizeCompatActivities.remove(r); return; } - // TODO(b/178327644) Update for per Task size compat - if (!r.inSizeCompatMode()) { - if (mLastCompatModeActivity != null) { + + if (r.isState(RESUMED) && r.inSizeCompatMode()) { + if (mActiveSizeCompatActivities.add(r)) { + // Trigger task event for new size compat activity. organizedTask.onSizeCompatActivityChanged(); } - mLastCompatModeActivity = null; return; } - if (mLastCompatModeActivity == r) { - return; + + if (mActiveSizeCompatActivities.remove(r)) { + // Trigger task event for activity no longer in foreground size compat. + organizedTask.onSizeCompatActivityChanged(); } - mLastCompatModeActivity = r; - organizedTask.onSizeCompatActivityChanged(); } boolean isUidPresent(int uid) { diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java new file mode 100644 index 000000000000..1552a96d699a --- /dev/null +++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java @@ -0,0 +1,380 @@ +/* + * 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.wm; + +import static android.app.UiModeManager.MODE_NIGHT_AUTO; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; + +import android.annotation.NonNull; +import android.os.Environment; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; + +/** + * Persist configuration for each package, only persist the change if some on attributes are + * different from the global configuration. This class only applies to packages with Activities. + */ +public class PackageConfigPersister { + private static final String TAG = PackageConfigPersister.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final String TAG_CONFIG = "config"; + private static final String ATTR_PACKAGE_NAME = "package_name"; + private static final String ATTR_NIGHT_MODE = "night_mode"; + + private static final String PACKAGE_DIRNAME = "package_configs"; + private static final String SUFFIX_FILE_NAME = "_config.xml"; + + private final PersisterQueue mPersisterQueue; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite = + new SparseArray<>(); + @GuardedBy("mLock") + private final SparseArray<HashMap<String, PackageConfigRecord>> mModified = + new SparseArray<>(); + + private static File getUserConfigsDir(int userId) { + return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME); + } + + PackageConfigPersister(PersisterQueue queue) { + mPersisterQueue = queue; + } + + @GuardedBy("mLock") + void loadUserPackages(int userId) { + synchronized (mLock) { + final File userConfigsDir = getUserConfigsDir(userId); + final File[] configFiles = userConfigsDir.listFiles(); + if (configFiles == null) { + Slog.v(TAG, "loadPackages: empty list files from " + userConfigsDir); + return; + } + + for (int fileIndex = 0; fileIndex < configFiles.length; ++fileIndex) { + final File configFile = configFiles[fileIndex]; + if (DEBUG) { + Slog.d(TAG, "loadPackages: userId=" + userId + + ", configFile=" + configFile.getName()); + } + if (!configFile.getName().endsWith(SUFFIX_FILE_NAME)) { + continue; + } + + try (InputStream is = new FileInputStream(configFile)) { + final TypedXmlPullParser in = Xml.resolvePullParser(is); + int event; + String packageName = null; + int nightMode = MODE_NIGHT_AUTO; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) + && event != XmlPullParser.END_TAG) { + final String name = in.getName(); + if (event == XmlPullParser.START_TAG) { + if (DEBUG) { + Slog.d(TAG, "loadPackages: START_TAG name=" + name); + } + if (TAG_CONFIG.equals(name)) { + for (int attIdx = in.getAttributeCount() - 1; attIdx >= 0; + --attIdx) { + final String attrName = in.getAttributeName(attIdx); + final String attrValue = in.getAttributeValue(attIdx); + switch (attrName) { + case ATTR_PACKAGE_NAME: + packageName = attrValue; + break; + case ATTR_NIGHT_MODE: + nightMode = Integer.parseInt(attrValue); + break; + } + } + } + } + XmlUtils.skipCurrentTag(in); + } + if (packageName != null) { + final PackageConfigRecord initRecord = + findRecordOrCreate(mModified, packageName, userId); + initRecord.mNightMode = nightMode; + if (DEBUG) { + Slog.d(TAG, "loadPackages: load one package " + initRecord); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } + } + } + } + + @GuardedBy("mLock") + void updateConfigIfNeeded(@NonNull ConfigurationContainer container, int userId, + String packageName) { + synchronized (mLock) { + final PackageConfigRecord modifiedRecord = findRecord(mModified, packageName, userId); + if (DEBUG) { + Slog.d(TAG, + "updateConfigIfNeeded record " + container + " find? " + modifiedRecord); + } + if (modifiedRecord != null) { + container.setOverrideNightMode(modifiedRecord.mNightMode); + } + } + } + + @GuardedBy("mLock") + void updateFromImpl(String packageName, int userId, + ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) { + synchronized (mLock) { + PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId); + record.mNightMode = impl.getNightMode(); + + if (record.isResetNightMode()) { + removePackage(record.mName, record.mUserId); + } else { + final PackageConfigRecord pendingRecord = + findRecord(mPendingWrite, record.mName, record.mUserId); + final PackageConfigRecord writeRecord; + if (pendingRecord == null) { + writeRecord = findRecordOrCreate(mPendingWrite, record.mName, + record.mUserId); + } else { + writeRecord = pendingRecord; + } + if (writeRecord.mNightMode == record.mNightMode) { + return; + } + writeRecord.mNightMode = record.mNightMode; + if (DEBUG) { + Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord); + } + mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */); + } + } + } + + @GuardedBy("mLock") + void removeUser(int userId) { + synchronized (mLock) { + final HashMap<String, PackageConfigRecord> modifyRecords = mModified.get(userId); + final HashMap<String, PackageConfigRecord> writeRecords = mPendingWrite.get(userId); + if ((modifyRecords == null || modifyRecords.size() == 0) + && (writeRecords == null || writeRecords.size() == 0)) { + return; + } + final HashMap<String, PackageConfigRecord> tempList = new HashMap<>(modifyRecords); + tempList.forEach((name, record) -> { + removePackage(record.mName, record.mUserId); + }); + } + } + + @GuardedBy("mLock") + void onPackageUninstall(String packageName) { + synchronized (mLock) { + for (int i = mModified.size() - 1; i > 0; i--) { + final int userId = mModified.keyAt(i); + removePackage(packageName, userId); + } + } + } + + private void removePackage(String packageName, int userId) { + if (DEBUG) { + Slog.d(TAG, "removePackage packageName :" + packageName + " userId " + userId); + } + final PackageConfigRecord record = findRecord(mPendingWrite, packageName, userId); + if (record != null) { + removeRecord(mPendingWrite, record); + mPersisterQueue.removeItems(item -> + item.mRecord.mName == record.mName + && item.mRecord.mUserId == record.mUserId, + WriteProcessItem.class); + } + + final PackageConfigRecord modifyRecord = findRecord(mModified, packageName, userId); + if (modifyRecord != null) { + removeRecord(mModified, modifyRecord); + mPersisterQueue.addItem(new DeletePackageItem(userId, packageName), + false /* flush */); + } + } + + // store a changed data so we don't need to get the process + static class PackageConfigRecord { + final String mName; + final int mUserId; + int mNightMode; + + PackageConfigRecord(String name, int userId) { + mName = name; + mUserId = userId; + } + + boolean isResetNightMode() { + return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM; + } + + @Override + public String toString() { + return "PackageConfigRecord package name: " + mName + " userId " + mUserId + + " nightMode " + mNightMode; + } + } + + private PackageConfigRecord findRecordOrCreate( + SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId) { + HashMap<String, PackageConfigRecord> records = list.get(userId); + if (records == null) { + records = new HashMap<>(); + list.put(userId, records); + } + PackageConfigRecord record = records.get(name); + if (record != null) { + return record; + } + record = new PackageConfigRecord(name, userId); + records.put(name, record); + return record; + } + + private PackageConfigRecord findRecord(SparseArray<HashMap<String, PackageConfigRecord>> list, + String name, int userId) { + HashMap<String, PackageConfigRecord> packages = list.get(userId); + if (packages == null) { + return null; + } + return packages.get(name); + } + + private void removeRecord(SparseArray<HashMap<String, PackageConfigRecord>> list, + PackageConfigRecord record) { + final HashMap<String, PackageConfigRecord> processes = list.get(record.mUserId); + if (processes != null) { + processes.remove(record.mName); + } + } + + private static class DeletePackageItem implements PersisterQueue.WriteQueueItem { + final int mUserId; + final String mPackageName; + + DeletePackageItem(int userId, String packageName) { + mUserId = userId; + mPackageName = packageName; + } + + @Override + public void process() { + File userConfigsDir = getUserConfigsDir(mUserId); + if (!userConfigsDir.isDirectory()) { + return; + } + final AtomicFile atomicFile = new AtomicFile(new File(userConfigsDir, + mPackageName + SUFFIX_FILE_NAME)); + if (atomicFile.exists()) { + atomicFile.delete(); + } + } + } + + private class WriteProcessItem implements PersisterQueue.WriteQueueItem { + final PackageConfigRecord mRecord; + + WriteProcessItem(PackageConfigRecord record) { + mRecord = record; + } + + @Override + public void process() { + // Write out one user. + byte[] data = null; + synchronized (mLock) { + try { + data = saveToXml(); + } catch (Exception e) { + } + removeRecord(mPendingWrite, mRecord); + } + if (data != null) { + // Write out xml file while not holding mService lock. + FileOutputStream file = null; + AtomicFile atomicFile = null; + try { + File userConfigsDir = getUserConfigsDir(mRecord.mUserId); + if (!userConfigsDir.isDirectory() && !userConfigsDir.mkdirs()) { + Slog.e(TAG, "Failure creating tasks directory for user " + mRecord.mUserId + + ": " + userConfigsDir); + return; + } + atomicFile = new AtomicFile(new File(userConfigsDir, + mRecord.mName + SUFFIX_FILE_NAME)); + file = atomicFile.startWrite(); + file.write(data); + atomicFile.finishWrite(file); + } catch (IOException e) { + if (file != null) { + atomicFile.failWrite(file); + } + Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e); + } + } + } + + private byte[] saveToXml() throws IOException { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + final TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(os); + + xmlSerializer.startDocument(null, true); + if (DEBUG) { + Slog.d(TAG, "Writing package configuration=" + mRecord); + } + xmlSerializer.startTag(null, TAG_CONFIG); + xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName); + xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode); + xmlSerializer.endTag(null, TAG_CONFIG); + xmlSerializer.endDocument(); + xmlSerializer.flush(); + + return os.toByteArray(); + } + } +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index bd93e045cdc7..6fbeaa4e944e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -847,8 +847,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mWmService.openSurfaceTransaction(); try { applySurfaceChangesTransaction(); - // Send any pending task-info changes that were queued-up during a layout deferment - mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); mWmService.mSyncEngine.onSurfacePlacement(); } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); @@ -861,6 +859,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } + // Send any pending task-info changes that were queued-up during a layout deferment + mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); mWmService.mAnimator.executeAfterPrepareSurfacesRunnables(); checkAppTransitionReady(surfacePlacer); @@ -2656,7 +2656,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void addStartingWindowsForVisibleActivities() { forAllActivities((r) -> { if (r.mVisibleRequested) { - r.showStartingWindow(null /* prev */, false /* newTask */, true /*taskSwitch*/); + r.showStartingWindow(true /*taskSwitch*/); } }); } diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 94e14dd0a6b8..ef4a40f4837c 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -60,7 +60,7 @@ public class StartingSurfaceController { final Task task = activity.getTask(); if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, - activity.token)) { + activity.token, theme)) { return new ShellStartingSurface(task); } return null; @@ -125,7 +125,8 @@ public class StartingSurfaceController { return mService.mTaskSnapshotController .createStartingSurface(activity, taskSnapshot); } - mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token); + mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token, + 0 /* launchTheme */); return new ShellStartingSurface(task); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9c8a997ec098..8bd4dfd054b6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -82,6 +82,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMA import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN; +import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; @@ -3881,6 +3882,13 @@ class Task extends WindowContainer<WindowContainer> { }); } + ActivityRecord getTopWaitSplashScreenActivity() { + return getActivity((r) -> { + return r.mHandleExitSplashScreen + && r.mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING; + }); + } + boolean isTopActivityFocusable() { final ActivityRecord r = topRunningActivity(); return r != null ? r.isFocusable() @@ -4267,6 +4275,9 @@ class Task extends WindowContainer<WindowContainer> { if (mainWindow != null) { info.mainWindowLayoutParams = mainWindow.getAttrs(); } + // If the developer has persist a different configuration, we need to override it to the + // starting window because persisted configuration does not effect to Task. + info.taskInfo.configuration.setTo(topActivity.getConfiguration()); } final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(); if (topFullscreenActivity != null) { @@ -6558,10 +6569,9 @@ class Task extends WindowContainer<WindowContainer> { Slog.i(TAG, "Restarting because process died: " + next); if (!next.hasBeenLaunched) { next.hasBeenLaunched = true; - } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null + } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null && lastFocusedRootTask.isTopRootTaskInDisplayArea()) { - next.showStartingWindow(null /* prev */, false /* newTask */, - false /* taskSwitch */); + next.showStartingWindow(false /* taskSwitch */); } mTaskSupervisor.startSpecificActivity(next, true, false); return true; @@ -6584,8 +6594,7 @@ class Task extends WindowContainer<WindowContainer> { next.hasBeenLaunched = true; } else { if (SHOW_APP_STARTING_PREVIEW) { - next.showStartingWindow(null /* prev */, false /* newTask */, - false /* taskSwich */); + next.showStartingWindow(false /* taskSwich */); } if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next); } @@ -6746,7 +6755,10 @@ class Task extends WindowContainer<WindowContainer> { prev = null; } } - r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity)); + final int splashScreenThemeResId = options != null + ? options.getSplashScreenThemeResId() : 0; + r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity), + splashScreenThemeResId); } } else { // If this is the first activity, don't do any fancy animations, diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 9fac3f003f70..c0bce6be8c54 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -117,8 +117,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return mTaskOrganizer.asBinder(); } - void addStartingWindow(Task task, IBinder appToken) { + void addStartingWindow(Task task, IBinder appToken, int launchTheme) { final StartingWindowInfo info = task.getStartingWindowInfo(); + if (launchTheme != 0) { + info.splashScreenThemeResId = launchTheme; + } mDeferTaskOrgCallbacksConsumer.accept(() -> { try { mTaskOrganizer.addStartingWindow(info, appToken); @@ -138,6 +141,16 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { }); } + void copySplashScreenView(Task task) { + mDeferTaskOrgCallbacksConsumer.accept(() -> { + try { + mTaskOrganizer.copySplashScreenView(task.mTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending copyStartingWindowView callback", e); + } + }); + } + SurfaceControl prepareLeash(Task task, boolean visible, String reason) { SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason); if (!task.mCreatedByOrganizer && !visible) { @@ -232,14 +245,18 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mUid = uid; } - void addStartingWindow(Task t, IBinder appToken) { - mOrganizer.addStartingWindow(t, appToken); + void addStartingWindow(Task t, IBinder appToken, int launchTheme) { + mOrganizer.addStartingWindow(t, appToken, launchTheme); } void removeStartingWindow(Task t) { mOrganizer.removeStartingWindow(t); } + void copySplashScreenView(Task t) { + mOrganizer.copySplashScreenView(t); + } + /** * Register this task with this state, but doesn't trigger the task appeared callback to * the organizer. @@ -465,14 +482,14 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode); } - boolean addStartingWindow(Task task, IBinder appToken) { + boolean addStartingWindow(Task task, IBinder appToken, int launchTheme) { final Task rootTask = task.getRootTask(); if (rootTask == null || rootTask.mTaskOrganizer == null) { return false; } final TaskOrganizerState state = mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); - state.addStartingWindow(task, appToken); + state.addStartingWindow(task, appToken, launchTheme); return true; } @@ -486,6 +503,17 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { state.removeStartingWindow(task); } + boolean copySplashScreenView(Task task) { + final Task rootTask = task.getRootTask(); + if (rootTask == null || rootTask.mTaskOrganizer == null) { + return false; + } + final TaskOrganizerState state = + mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); + state.copySplashScreenView(task); + return true; + } + void onTaskAppeared(ITaskOrganizer organizer, Task task) { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); if (state != null && state.addTask(task)) { @@ -646,7 +674,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // Remove and add for re-ordering. mPendingTaskEvents.remove(pending); } - pending.mForce = force; + pending.mForce |= force; mPendingTaskEvents.add(pending); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c54c9786c00d..5dc5ab767b2e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7514,8 +7514,8 @@ public class WindowManagerService extends IWindowManager.Stub if (spec != null) { result.setTo(spec); } - spec.scale *= windowState.mGlobalScale; - return spec; + result.scale *= windowState.mGlobalScale; + return result; } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 264a3b4edfa6..c3620235d3df 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -256,6 +256,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } onConfigurationChanged(atm.getGlobalConfiguration()); + mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName); } public void setPid(int pid) { @@ -802,6 +803,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return false; } + void updateNightModeForAllActivities(int nightMode) { + for (int i = mActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = mActivities.get(i); + r.setOverrideNightMode(nightMode); + } + } + public void clearPackagePreferredForHomeActivities() { synchronized (mAtm.mGlobalLock) { for (int i = mActivities.size() - 1; i >= 0; --i) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 1c4b03498144..11e3ecfbb90d 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -30,6 +30,7 @@ cc_library_static { "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp", "com_android_server_ConsumerIrService.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", + "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", "com_android_server_input_InputManagerService.cpp", @@ -54,7 +55,7 @@ cc_library_static { "com_android_server_UsbMidiDevice.cpp", "com_android_server_UsbHostManager.cpp", "com_android_server_vibrator_VibratorController.cpp", - "com_android_server_VibratorManagerService.cpp", + "com_android_server_vibrator_VibratorManagerService.cpp", "com_android_server_PersistentDataBlockService.cpp", "com_android_server_am_LowMemDetector.cpp", "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp", @@ -149,7 +150,7 @@ cc_defaults { "android.hardware.power@1.1", "android.hardware.power-V1-cpp", "android.hardware.power.stats@1.0", - "android.hardware.power.stats-ndk_platform", + "android.hardware.power.stats-V1-ndk_platform", "android.hardware.thermal@1.0", "android.hardware.tv.input@1.0", "android.hardware.vibrator-V2-cpp", diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 9a8942b47428..d076434e7d90 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -1,10 +1,6 @@ # Display per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com -# Haptics -per-file com_android_server_vibrator_VibratorController.cpp = michaelwr@google.com -per-file com_android_server_VibratorManagerService.cpp = michaelwr@google.com - # Input per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com, svv@google.com diff --git a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp index ea5e7183c905..ea5e7183c905 100644 --- a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index a6029cd28029..89b931d35c40 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -29,7 +29,7 @@ #include <vibratorservice/VibratorHalController.h> -#include "com_android_server_VibratorManagerService.h" +#include "com_android_server_vibrator_VibratorManagerService.h" namespace V1_0 = android::hardware::vibrator::V1_0; namespace V1_1 = android::hardware::vibrator::V1_1; @@ -73,7 +73,8 @@ static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) == static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK)); static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) { - vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager(); + vibrator::ManagerHalController* manager = + android_server_vibrator_VibratorManagerService_getManager(); if (manager == nullptr) { return nullptr; } diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp index 5dbb71a4976c..a47ab9d27c17 100644 --- a/services/core/jni/com_android_server_VibratorManagerService.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp @@ -26,7 +26,7 @@ #include <vibratorservice/VibratorManagerHalController.h> -#include "com_android_server_VibratorManagerService.h" +#include "com_android_server_vibrator_VibratorManagerService.h" namespace android { @@ -64,7 +64,7 @@ private: const jobject mCallbackListener; }; -vibrator::ManagerHalController* android_server_VibratorManagerService_getManager() { +vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager() { std::lock_guard<std::mutex> lock(gManagerMutex); return gManager; } @@ -156,10 +156,11 @@ static void nativeCancelSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr service->hal()->cancelSynced(); } +inline static constexpr auto sNativeInitMethodSignature = + "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J"; + static const JNINativeMethod method_table[] = { - {"nativeInit", - "(Lcom/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener;)J", - (void*)nativeInit}, + {"nativeInit", sNativeInitMethodSignature, (void*)nativeInit}, {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer}, {"nativeGetCapabilities", "(J)J", (void*)nativeGetCapabilities}, {"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds}, @@ -168,15 +169,15 @@ static const JNINativeMethod method_table[] = { {"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced}, }; -int register_android_server_VibratorManagerService(JavaVM* jvm, JNIEnv* env) { +int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) { sJvm = jvm; auto listenerClassName = - "com/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener"; + "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener"; jclass listenerClass = FindClassOrDie(env, listenerClassName); sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V"); - return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table, - NELEM(method_table)); + return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService", + method_table, NELEM(method_table)); } }; // namespace android diff --git a/services/core/jni/com_android_server_VibratorManagerService.h b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h index 22950c5036e4..9924e24f14d0 100644 --- a/services/core/jni/com_android_server_VibratorManagerService.h +++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 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. @@ -21,7 +21,7 @@ namespace android { -extern vibrator::ManagerHalController* android_server_VibratorManagerService_getManager(); +extern vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager(); } // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 34f604895721..1815f0cd44c9 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -39,8 +39,9 @@ int register_android_server_UsbMidiDevice(JNIEnv* env); int register_android_server_UsbHostManager(JNIEnv* env); int register_android_server_vr_VrManagerService(JNIEnv* env); int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env); -int register_android_server_VibratorManagerService(JavaVM* vm, JNIEnv* env); +int register_android_server_vibrator_VibratorManagerService(JavaVM* vm, JNIEnv* env); int register_android_server_location_GnssLocationProvider(JNIEnv* env); +int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*); int register_android_server_tv_TvUinputBridge(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); @@ -54,10 +55,8 @@ int register_android_server_net_NetworkStatsService(JNIEnv* env); int register_android_server_security_VerityUtils(JNIEnv* env); int register_android_server_am_CachedAppOptimizer(JNIEnv* env); int register_android_server_am_LowMemDetector(JNIEnv* env); -int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( - JNIEnv* env); -int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker( - JNIEnv* env); +int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env); +int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env); int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env); int register_android_server_AdbDebuggingManager(JNIEnv* env); int register_android_server_FaceService(JNIEnv* env); @@ -90,9 +89,10 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_UsbHostManager(env); register_android_server_vr_VrManagerService(env); register_android_server_vibrator_VibratorController(vm, env); - register_android_server_VibratorManagerService(vm, env); + register_android_server_vibrator_VibratorManagerService(vm, env); register_android_server_SystemServer(env); register_android_server_location_GnssLocationProvider(env); + register_android_server_connectivity_Vpn(env); register_android_server_devicepolicy_CryptoTestHelper(env); register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); @@ -109,10 +109,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_security_VerityUtils(env); register_android_server_am_CachedAppOptimizer(env); register_android_server_am_LowMemDetector(env); - register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( - env); - register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker( - env); + register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env); + register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env); register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env); register_android_server_AdbDebuggingManager(env); register_android_server_FaceService(env); diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd index e27e1b8ca89d..1406dbb12e02 100644 --- a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd +++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd @@ -27,6 +27,13 @@ <xs:attribute type="xs:boolean" name="enabled" use="required" /> </xs:complexType> + <xs:complexType name="raw-override-value"> + <xs:attribute type="xs:string" name="packageName" use="required" /> + <xs:attribute type="xs:long" name="minVersionCode" /> + <xs:attribute type="xs:long" name="maxVersionCode" /> + <xs:attribute type="xs:boolean" name="enabled" use="required" /> + </xs:complexType> + <xs:complexType name="change-overrides"> <xs:attribute type="xs:long" name="changeId" use="required"/> <xs:element name="validated"> @@ -43,6 +50,13 @@ </xs:sequence> </xs:complexType> </xs:element> + <xs:element name="raw"> + <xs:complexType> + <xs:sequence> + <xs:element name="raw-override-value" type="raw-override-value" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> </xs:complexType> <xs:element name="overrides"> diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt index 08b82072747b..a5ccffcfbb2b 100644 --- a/services/core/xsd/platform-compat/overrides/schema/current.txt +++ b/services/core/xsd/platform-compat/overrides/schema/current.txt @@ -5,9 +5,11 @@ package com.android.server.compat.overrides { ctor public ChangeOverrides(); method public long getChangeId(); method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred(); + method public com.android.server.compat.overrides.ChangeOverrides.Raw getRaw(); method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated(); method public void setChangeId(long); method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred); + method public void setRaw(com.android.server.compat.overrides.ChangeOverrides.Raw); method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated); } @@ -16,6 +18,11 @@ package com.android.server.compat.overrides { method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); } + public static class ChangeOverrides.Raw { + ctor public ChangeOverrides.Raw(); + method public java.util.List<com.android.server.compat.overrides.RawOverrideValue> getRawOverrideValue(); + } + public static class ChangeOverrides.Validated { ctor public ChangeOverrides.Validated(); method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); @@ -34,6 +41,18 @@ package com.android.server.compat.overrides { method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides(); } + public class RawOverrideValue { + ctor public RawOverrideValue(); + method public boolean getEnabled(); + method public long getMaxVersionCode(); + method public long getMinVersionCode(); + method public String getPackageName(); + method public void setEnabled(boolean); + method public void setMaxVersionCode(long); + method public void setMinVersionCode(long); + method public void setPackageName(String); + } + public class XmlParser { ctor public XmlParser(); method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 07eb7bf8f9a0..498ee38d442a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -62,6 +62,7 @@ import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; +import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; @@ -92,7 +93,6 @@ import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_REMOVE_N import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED; -import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -157,9 +157,9 @@ import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.DeviceStateCache; @@ -781,8 +781,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * however it's too early in the boot process to register with IIpConnectivityMetrics * to listen for events. */ - if (Intent.ACTION_USER_STARTED.equals(action) - && userHandle == mOwners.getDeviceOwnerUserId()) { + if (Intent.ACTION_USER_STARTED.equals(action) && userHandle == UserHandle.USER_SYSTEM) { synchronized (getLockObject()) { if (isNetworkLoggingEnabledInternalLocked()) { setNetworkLoggingActiveInternal(true); @@ -1101,7 +1100,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) { int reason = getUnsafeOperationReason(operation); - if (reason == UNSAFE_OPERATION_REASON_NONE) return; + if (reason == OPERATION_SAFETY_REASON_NONE) return; if (mSafetyChecker == null) { // Happens on CTS after it's set just once (by OneTimeSafetyChecker) @@ -1114,23 +1113,28 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Returns whether it's safe to execute the given {@code operation}, and why. */ - @UnsafeOperationReason + @OperationSafetyReason int getUnsafeOperationReason(@DevicePolicyOperation int operation) { - return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE + return mSafetyChecker == null ? OPERATION_SAFETY_REASON_NONE : mSafetyChecker.getUnsafeOperationReason(operation); } @Override public void setNextOperationSafety(@DevicePolicyOperation int operation, - @UnsafeOperationReason int reason) { + @OperationSafetyReason int reason) { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)", DevicePolicyManager.operationToString(operation), - DevicePolicyManager.unsafeOperationReasonToString(reason))); + DevicePolicyManager.operationSafetyReasonToString(reason))); mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason); } + @Override + public boolean isSafeOperation(@OperationSafetyReason int reason) { + return mSafetyChecker == null ? true : mSafetyChecker.isSafeOperation(reason); + } + // Used by DevicePolicyManagerServiceShellCommand List<OwnerDto> listAllOwners() { Preconditions.checkCallAuthorization( @@ -6367,7 +6371,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); return mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist( + () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist( caller.getUserId())); } @@ -7522,19 +7526,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent); } - private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) { - sendActiveAdminCommand(action, extras, userHandle, - mOwners.getProfileOwnerComponent(userHandle)); + void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) { + if (userId == UserHandle.USER_ALL) { + userId = UserHandle.USER_SYSTEM; + } + ComponentName receiverComponent = null; + if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) { + receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, userId); + } + if (receiverComponent == null) { + receiverComponent = getOwnerComponent(userId); + } + sendActiveAdminCommand(action, extras, userId, receiverComponent); + } + + private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) { + sendActiveAdminCommand(action, extras, userId, + mOwners.getProfileOwnerComponent(userId)); } private void sendActiveAdminCommand(String action, Bundle extras, - int userHandle, ComponentName receiverComponent) { + @UserIdInt int userId, ComponentName receiverComponent) { + if (VERBOSE_LOG) { + Slog.v(LOG_TAG, "sending intent " + action + " to " + + receiverComponent.flattenToShortString() + " on user " + userId); + } final Intent intent = new Intent(action); intent.setComponent(receiverComponent); if (extras != null) { intent.putExtras(extras); } - mContext.sendBroadcastAsUser(intent, UserHandle.of(userHandle)); + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } private void sendOwnerChangedBroadcast(String broadcast, int userId) { @@ -8342,6 +8364,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { deleteTransferOwnershipBundleLocked(userId); toggleBackupServiceActive(userId, true); applyManagedProfileRestrictionIfDeviceOwnerLocked(); + setNetworkLoggingActiveInternal(false); } @Override @@ -12224,6 +12247,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { packageName, findInteractAcrossProfilesResetMode(packageName), userId); } + @Override + public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason, + boolean isSafe) { + // TODO(b/178494483): use EventLog instead + // TODO(b/178494483): log metrics? + if (VERBOSE_LOG) { + Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b", + DevicePolicyManager.operationSafetyReasonToString(reason), isSafe)); + } + + Preconditions.checkArgument(mSafetyChecker == checker, + "invalid checker: should be %s, was %s", mSafetyChecker, checker); + + Bundle extras = new Bundle(); + extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason); + extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe); + + // TODO(b/178494483): add CTS test + sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, + extras); + for (int profileOwnerId : mOwners.getProfileOwnerKeys()) { + sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, + extras, profileOwnerId); + } + } + private @Mode int findInteractAcrossProfilesResetMode(String packageName) { return getDefaultCrossProfilePackages().contains(packageName) ? AppOpsManager.MODE_ALLOWED @@ -14096,7 +14145,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); synchronized (getLockObject()) { @@ -14104,11 +14155,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // already in the requested state return; } - ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - deviceOwner.isNetworkLoggingEnabled = enabled; + final ActiveAdmin activeAdmin = getDeviceOrProfileOwnerAdminLocked(caller.getUserId()); + activeAdmin.isNetworkLoggingEnabled = enabled; if (!enabled) { - deviceOwner.numNetworkLoggingNotifications = 0; - deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + activeAdmin.numNetworkLoggingNotifications = 0; + activeAdmin.lastNetworkLoggingNotificationTimeMs = 0; } saveSettingsLocked(caller.getUserId()); setNetworkLoggingActiveInternal(enabled); @@ -14126,7 +14177,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { mInjector.binderWithCleanCallingIdentity(() -> { if (active) { - mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal()); + if (mNetworkLogger == null) { + final int affectedUserId = getNetworkLoggingAffectedUser(); + mNetworkLogger = new NetworkLogger(this, + mInjector.getPackageManagerInternal(), + affectedUserId == UserHandle.USER_SYSTEM + ? UserHandle.USER_ALL : affectedUserId); + } if (!mNetworkLogger.startNetworkLogging()) { mNetworkLogger = null; Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging" @@ -14146,6 +14203,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private @UserIdInt int getNetworkLoggingAffectedUser() { + synchronized (getLockObject()) { + if (mOwners.hasDeviceOwner()) { + return mOwners.getDeviceOwnerUserId(); + } else { + return mInjector.binderWithCleanCallingIdentity( + () -> getManagedUserId(UserHandle.USER_SYSTEM)); + } + } + } + + private ActiveAdmin getNetworkLoggingControllingAdminLocked() { + int affectedUserId = getNetworkLoggingAffectedUser(); + if (affectedUserId < 0) { + return null; + } + return getDeviceOrProfileOwnerAdminLocked(affectedUserId); + } + @Override public long forceNetworkLogs() { Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), @@ -14166,10 +14242,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @GuardedBy("getLockObject()") private void maybePauseDeviceWideLoggingLocked() { if (!areAllUsersAffiliatedWithDeviceLocked()) { - Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be " - + "paused if enabled."); - if (mNetworkLogger != null) { - mNetworkLogger.pause(); + if (mOwners.hasDeviceOwner()) { + Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be " + + "paused if enabled."); + if (mNetworkLogger != null) { + mNetworkLogger.pause(); + } } if (!isOrganizationOwnedDeviceWithManagedProfile()) { Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be " @@ -14188,7 +14266,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (allUsersAffiliated || orgOwnedProfileDevice) { mSecurityLogMonitor.resume(); } - if (allUsersAffiliated) { + // If there is no device owner, then per-user network logging may be enabled for the + // managed profile. In which case, all users do not need to be affiliated. + if (allUsersAffiliated || !mOwners.hasDeviceOwner()) { if (mNetworkLogger != null) { mNetworkLogger.resume(); } @@ -14215,7 +14295,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)) || hasCallingOrSelfPermission(permission.MANAGE_USERS)); @@ -14225,8 +14307,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private boolean isNetworkLoggingEnabledInternalLocked() { - ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled; + ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked(); + return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled; } /* @@ -14243,9 +14325,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } final CallerIdentity caller = getCallerIdentity(admin, packageName); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); - checkAllUsersAreAffiliatedWithDevice(); + if (mOwners.hasDeviceOwner()) { + checkAllUsersAreAffiliatedWithDevice(); + } synchronized (getLockObject()) { if (mNetworkLogger == null || !isNetworkLoggingEnabledInternalLocked()) { @@ -14258,34 +14344,35 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); final long currentTime = System.currentTimeMillis(); - DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM); + DevicePolicyData policyData = getUserData(caller.getUserId()); if (currentTime > policyData.mLastNetworkLogsRetrievalTime) { policyData.mLastNetworkLogsRetrievalTime = currentTime; - saveSettingsLocked(UserHandle.USER_SYSTEM); + saveSettingsLocked(caller.getUserId()); } return mNetworkLogger.retrieveLogs(batchToken); } } private void sendNetworkLoggingNotificationLocked() { - final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) { + ensureLocked(); + final ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked(); + if (activeAdmin == null || !activeAdmin.isNetworkLoggingEnabled) { return; } - if (deviceOwner.numNetworkLoggingNotifications >= - ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { + if (activeAdmin.numNetworkLoggingNotifications + >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { return; } final long now = System.currentTimeMillis(); - if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) { + if (now - activeAdmin.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) { return; } - deviceOwner.numNetworkLoggingNotifications++; - if (deviceOwner.numNetworkLoggingNotifications + activeAdmin.numNetworkLoggingNotifications++; + if (activeAdmin.numNetworkLoggingNotifications >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { - deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + activeAdmin.lastNetworkLoggingNotificationTimeMs = 0; } else { - deviceOwner.lastNetworkLoggingNotificationTimeMs = now; + activeAdmin.lastNetworkLoggingNotificationTimeMs = now; } final PackageManagerInternal pm = mInjector.getPackageManagerInternal(); final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG); @@ -14305,7 +14392,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .bigText(mContext.getString(R.string.network_logging_notification_text))) .build(); mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification); - saveSettingsLocked(mOwners.getDeviceOwnerUserId()); + saveSettingsLocked(activeAdmin.getUserHandle().getIdentifier()); } /** @@ -14369,8 +14456,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long getLastNetworkLogRetrievalTime() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller)); - return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime; + + Preconditions.checkCallAuthorization(isDeviceOwner(caller) + || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())) + || canManageUsers(caller)); + final int affectedUserId = getNetworkLoggingAffectedUser(); + return affectedUserId >= 0 ? getUserData(affectedUserId).mLastNetworkLogsRetrievalTime : -1; } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java index 222c987d906f..5484a148b0b6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java @@ -27,6 +27,7 @@ import java.util.Objects; final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe"; + private static final String CMD_IS_SAFE_OPERATION_BY_REASON = "is-operation-safe-by-reason"; private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe"; private static final String CMD_LIST_OWNERS = "list-owners"; @@ -53,6 +54,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { switch (cmd) { case CMD_IS_SAFE_OPERATION: return runIsSafeOperation(pw); + case CMD_IS_SAFE_OPERATION_BY_REASON: + return runIsSafeOperationByReason(pw); case CMD_SET_SAFE_OPERATION: return runSetSafeOperation(pw); case CMD_LIST_OWNERS: @@ -73,12 +76,13 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { return -1; } - private void showHelp(PrintWriter pw) { pw.printf(" help\n"); pw.printf(" Prints this help text.\n\n"); pw.printf(" %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION); pw.printf(" Checks if the give operation is safe \n\n"); + pw.printf(" %s <REASON_ID>\n", CMD_IS_SAFE_OPERATION_BY_REASON); + pw.printf(" Checks if the operations are safe for the given reason\n\n"); pw.printf(" %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION); pw.printf(" Emulates the result of the next call to check if the given operation is safe" + " \n\n"); @@ -89,10 +93,19 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private int runIsSafeOperation(PrintWriter pw) { int operation = Integer.parseInt(getNextArgRequired()); int reason = mService.getUnsafeOperationReason(operation); - boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE; + boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE; pw.printf("Operation %s is %b. Reason: %s\n", DevicePolicyManager.operationToString(operation), safe, - DevicePolicyManager.unsafeOperationReasonToString(reason)); + DevicePolicyManager.operationSafetyReasonToString(reason)); + return 0; + } + + private int runIsSafeOperationByReason(PrintWriter pw) { + int reason = Integer.parseInt(getNextArgRequired()); + boolean safe = mService.isSafeOperation(reason); + pw.printf("Operations affected by %s are %s\n", + DevicePolicyManager.operationSafetyReasonToString(reason), + (safe ? "SAFE" : "UNSAFE")); return 0; } @@ -102,7 +115,7 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { mService.setNextOperationSafety(operation, reason); pw.printf("Next call to check operation %s will return %s\n", DevicePolicyManager.operationToString(operation), - DevicePolicyManager.unsafeOperationReasonToString(reason)); + DevicePolicyManager.operationSafetyReasonToString(reason)); return 0; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java index e9b2d7f56108..8843a5d5306e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import android.util.Slog; @@ -47,6 +48,10 @@ final class NetworkLogger { private final PackageManagerInternal mPm; private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false); + // The target userId to collect network events on. The target userId will be + // {@link android.os.UserHandle#USER_ALL} if network events should be collected for all users. + private final int mTargetUserId; + private IIpConnectivityMetrics mIpConnectivityMetrics; private ServiceThread mHandlerThread; private NetworkLoggingHandler mNetworkLoggingHandler; @@ -58,6 +63,11 @@ final class NetworkLogger { if (!mIsLoggingEnabled.get()) { return; } + // If the network logging was enabled by the profile owner, then do not + // include events in the personal profile. + if (!shouldLogNetworkEvent(uid)) { + return; + } DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount, mPm.getNameForUid(uid), timestamp); sendNetworkEvent(dnsEvent); @@ -68,6 +78,11 @@ final class NetworkLogger { if (!mIsLoggingEnabled.get()) { return; } + // If the network logging was enabled by the profile owner, then do not + // include events in the personal profile. + if (!shouldLogNetworkEvent(uid)) { + return; + } ConnectEvent connectEvent = new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), timestamp); sendNetworkEvent(connectEvent); @@ -81,11 +96,17 @@ final class NetworkLogger { msg.setData(bundle); mNetworkLoggingHandler.sendMessage(msg); } + + private boolean shouldLogNetworkEvent(int uid) { + return mTargetUserId == UserHandle.USER_ALL + || mTargetUserId == UserHandle.getUserId(uid); + } }; - NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm) { + NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm, int targetUserId) { mDpm = dpm; mPm = pm; + mTargetUserId = targetUserId; } private boolean checkIpConnectivityMetricsService() { @@ -114,7 +135,7 @@ final class NetworkLogger { /* allowIo */ false); mHandlerThread.start(); mNetworkLoggingHandler = new NetworkLoggingHandler(mHandlerThread.getLooper(), - mDpm); + mDpm, mTargetUserId); mNetworkLoggingHandler.scheduleBatchFinalization(); mIsLoggingEnabled.set(true); return true; @@ -153,7 +174,7 @@ final class NetworkLogger { } /** - * If logs are being collected, keep collecting them but stop notifying the device owner that + * If logs are being collected, keep collecting them but stop notifying the admin that * new logs are available (since they cannot be retrieved) */ void pause() { @@ -163,11 +184,11 @@ final class NetworkLogger { } /** - * If logs are being collected, start notifying the device owner when logs are ready to be + * If logs are being collected, start notifying the admin when logs are ready to be * collected again (if it was paused). * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt - * to notify the device owner. Therefore calling identity should be cleared before calling it - * (in case the method is called from a user other than the DO's user). + * to notify the admin. Therefore calling identity should be cleared before calling it + * (in case the method is called from a user other than the admin's user). */ void resume() { if (mNetworkLoggingHandler != null) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java index 0a7070ffe4f6..84e89a08e1f4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -50,7 +50,7 @@ final class NetworkLoggingHandler extends Handler { private static final int MAX_EVENTS_PER_BATCH = 1200; /** - * Maximum number of batches to store in memory. If more batches are generated and the DO + * Maximum number of batches to store in memory. If more batches are generated and the admin * doesn't fetch them, we will discard the oldest one. */ private static final int MAX_BATCHES = 5; @@ -74,6 +74,7 @@ final class NetworkLoggingHandler extends Handler { private final AlarmManager mAlarmManager; private long mId; + private int mTargetUserId; private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() { @Override @@ -82,10 +83,10 @@ final class NetworkLoggingHandler extends Handler { + mNetworkEvents.size() + " pending events."); Bundle notificationExtras = null; synchronized (NetworkLoggingHandler.this) { - notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked(); + notificationExtras = finalizeBatchAndBuildAdminMessageLocked(); } if (notificationExtras != null) { - notifyDeviceOwner(notificationExtras); + notifyDeviceOwnerOrProfileOwner(notificationExtras); } } }; @@ -98,8 +99,8 @@ final class NetworkLoggingHandler extends Handler { private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>(); /** - * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already - * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}. + * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the admin. + * Already retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}. */ @GuardedBy("this") private final LongSparseArray<ArrayList<NetworkEvent>> mBatches = @@ -115,16 +116,18 @@ final class NetworkLoggingHandler extends Handler { @GuardedBy("this") private long mLastRetrievedBatchToken; - NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { - this(looper, dpm, 0 /* event id */); + NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, int targetUserId) { + this(looper, dpm, 0 /* event id */, targetUserId); } @VisibleForTesting - NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) { + NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id, + int targetUserId) { super(looper); this.mDpm = dpm; this.mAlarmManager = mDpm.mInjector.getAlarmManager(); this.mId = id; + this.mTargetUserId = targetUserId; } @Override @@ -137,11 +140,11 @@ final class NetworkLoggingHandler extends Handler { synchronized (NetworkLoggingHandler.this) { mNetworkEvents.add(networkEvent); if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) { - notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked(); + notificationExtras = finalizeBatchAndBuildAdminMessageLocked(); } } if (notificationExtras != null) { - notifyDeviceOwner(notificationExtras); + notifyDeviceOwnerOrProfileOwner(notificationExtras); } } break; @@ -176,10 +179,10 @@ final class NetworkLoggingHandler extends Handler { if (toWaitNanos > 0) { return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up. } - notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked(); + notificationExtras = finalizeBatchAndBuildAdminMessageLocked(); } if (notificationExtras != null) { - notifyDeviceOwner(notificationExtras); + notifyDeviceOwnerOrProfileOwner(notificationExtras); } return 0; } @@ -201,14 +204,15 @@ final class NetworkLoggingHandler extends Handler { + ", LastRetrievedBatch=" + mLastRetrievedBatchToken); mPaused = false; - // If there is a batch ready that the device owner hasn't been notified about, do it now. + // If there is a batch ready that the device owner or profile owner hasn't been + // notified about, do it now. if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) { scheduleBatchFinalization(); - notificationExtras = buildDeviceOwnerMessageLocked(); + notificationExtras = buildAdminMessageLocked(); } } if (notificationExtras != null) { - notifyDeviceOwner(notificationExtras); + notifyDeviceOwnerOrProfileOwner(notificationExtras); } } @@ -219,8 +223,8 @@ final class NetworkLoggingHandler extends Handler { } @GuardedBy("this") - /** @returns extras if a message should be sent to the device owner */ - private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() { + /** @return extras if a message should be sent to the device owner or profile owner */ + private Bundle finalizeBatchAndBuildAdminMessageLocked() { mLastFinalizationNanos = System.nanoTime(); Bundle notificationExtras = null; if (mNetworkEvents.size() > 0) { @@ -243,10 +247,10 @@ final class NetworkLoggingHandler extends Handler { mBatches.append(mCurrentBatchToken, mNetworkEvents); mNetworkEvents = new ArrayList<>(); if (!mPaused) { - notificationExtras = buildDeviceOwnerMessageLocked(); + notificationExtras = buildAdminMessageLocked(); } } else { - // Don't notify the DO, since there are no events; DPC can still retrieve + // Don't notify the admin, since there are no events; DPC can still retrieve // the last full batch if not paused. Slog.d(TAG, "Was about to finalize the batch, but there were no events to send to" + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken); @@ -257,9 +261,9 @@ final class NetworkLoggingHandler extends Handler { } @GuardedBy("this") - /** Build extras notification to the DO. Should only be called when there + /** Build extras notification to the admin. Should only be called when there is a batch available. */ - private Bundle buildDeviceOwnerMessageLocked() { + private Bundle buildAdminMessageLocked() { final Bundle extras = new Bundle(); final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size(); extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken); @@ -267,16 +271,18 @@ final class NetworkLoggingHandler extends Handler { return extras; } - /** Sends a notification to the DO. Should not hold locks as DevicePolicyManagerService may - call into NetworkLoggingHandler. */ - private void notifyDeviceOwner(Bundle extras) { - Slog.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " - + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1)); + /** Sends a notification to the device owner or profile owner. Should not hold locks as + DevicePolicyManagerService may call into NetworkLoggingHandler. */ + private void notifyDeviceOwnerOrProfileOwner(Bundle extras) { if (Thread.holdsLock(this)) { Slog.wtfStack(TAG, "Shouldn't be called with NetworkLoggingHandler lock held"); return; } - mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); + Slog.d(TAG, "Sending network logging batch broadcast to device owner or profile owner, " + + "batchToken: " + + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1)); + mDpm.sendDeviceOwnerOrProfileOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, + extras, mTargetUserId); } synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java index 883f95d930a0..7de1bd50a9eb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -15,16 +15,18 @@ */ package com.android.server.devicepolicy; -import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE; +import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE; +import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString; import static android.app.admin.DevicePolicyManager.operationToString; -import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; -import android.app.admin.DevicePolicyManager.UnsafeOperationReason; +import android.app.admin.DevicePolicyManager.OperationSafetyReason; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicySafetyChecker; import android.util.Slog; import com.android.internal.os.IResultReceiver; +import com.android.server.LocalServices; import java.util.Objects; @@ -43,10 +45,10 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { private final DevicePolicyManagerService mService; private final DevicePolicySafetyChecker mRealSafetyChecker; private final @DevicePolicyOperation int mOperation; - private final @UnsafeOperationReason int mReason; + private final @OperationSafetyReason int mReason; OneTimeSafetyChecker(DevicePolicyManagerService service, - @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) { + @DevicePolicyOperation int operation, @OperationSafetyReason int reason) { mService = Objects.requireNonNull(service); mOperation = operation; mReason = reason; @@ -55,24 +57,42 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { } @Override - @UnsafeOperationReason + @OperationSafetyReason public int getUnsafeOperationReason(@DevicePolicyOperation int operation) { String name = operationToString(operation); - int reason = UNSAFE_OPERATION_REASON_NONE; + Slog.i(TAG, "getUnsafeOperationReason(" + name + ")"); + int reason = OPERATION_SAFETY_REASON_NONE; if (operation == mOperation) { reason = mReason; } else { Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name + ", should be " + operationToString(mOperation)); } - Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning " - + unsafeOperationReasonToString(reason) + String reasonName = operationSafetyReasonToString(reason); + DevicePolicyManagerInternal dpmi = LocalServices + .getService(DevicePolicyManagerInternal.class); + + Slog.i(TAG, "notifying " + reasonName + " is active"); + dpmi.notifyUnsafeOperationStateChanged(this, reason, true); + + Slog.i(TAG, "notifying " + reasonName + " is inactive"); + dpmi.notifyUnsafeOperationStateChanged(this, reason, false); + + Slog.i(TAG, "returning " + reasonName + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker); mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker); return reason; } @Override + public boolean isSafeOperation(@OperationSafetyReason int reason) { + boolean safe = mReason != reason; + Slog.i(TAG, "isSafeOperation(" + operationSafetyReasonToString(reason) + "): " + safe); + + return safe; + } + + @Override public void onFactoryReset(IResultReceiver callback) { throw new UnsupportedOperationException(); } diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java index 1801f3bca30e..a2768c637d79 100644 --- a/services/java/com/android/server/SystemConfigService.java +++ b/services/java/com/android/server/SystemConfigService.java @@ -21,6 +21,10 @@ import static java.util.stream.Collectors.toMap; import android.Manifest; import android.content.Context; import android.os.ISystemConfig; +import android.util.ArraySet; +import android.util.SparseArray; + +import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.List; @@ -64,6 +68,22 @@ public class SystemConfigService extends SystemService { return SystemConfig.getInstance() .getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); } + + @Override + public int[] getSystemPermissionUids(String permissionName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, + "getSystemPermissionUids requires GET_RUNTIME_PERMISSIONS"); + final List<Integer> uids = new ArrayList<>(); + final SparseArray<ArraySet<String>> systemPermissions = + SystemConfig.getInstance().getSystemPermissions(); + for (int i = 0; i < systemPermissions.size(); i++) { + final ArraySet<String> permissions = systemPermissions.valueAt(i); + if (permissions != null && permissions.contains(permissionName)) { + uids.add(systemPermissions.keyAt(i)); + } + } + return ArrayUtils.convertToIntArray(uids); + } }; public SystemConfigService(Context context) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a0e5c5deef61..bd2046490ec9 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -195,6 +195,7 @@ import com.android.server.twilight.TwilightService; import com.android.server.uri.UriGrantsManagerService; import com.android.server.usage.UsageStatsService; import com.android.server.utils.TimingsTraceAndSlog; +import com.android.server.vibrator.VibratorManagerService; import com.android.server.vr.VrManagerService; import com.android.server.webkit.WebViewUpdateService; import com.android.server.wm.ActivityTaskManagerService; @@ -1295,6 +1296,7 @@ public final class SystemServer implements Dumpable { IStorageManager storageManager = null; NetworkManagementService networkManagement = null; IpSecService ipSecService = null; + VpnManagerService vpnManager = null; VcnManagementService vcnManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; @@ -1883,6 +1885,15 @@ public final class SystemServer implements Dumpable { networkPolicy.bindConnectivityManager(connectivity); t.traceEnd(); + t.traceBegin("StartVpnManagerService"); + try { + vpnManager = VpnManagerService.create(context); + ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager); + } catch (Throwable e) { + reportWtf("starting VPN Manager Service", e); + } + t.traceEnd(); + t.traceBegin("StartVcnManagementService"); try { vcnManagement = VcnManagementService.create(context); @@ -2611,6 +2622,7 @@ public final class SystemServer implements Dumpable { final MediaRouterService mediaRouterF = mediaRouter; final MmsServiceBroker mmsServiceF = mmsService; final IpSecService ipSecServiceF = ipSecService; + final VpnManagerService vpnManagerF = vpnManager; final VcnManagementService vcnManagementF = vcnManagement; final WindowManagerService windowManagerF = wm; final ConnectivityManager connectivityF = (ConnectivityManager) @@ -2725,6 +2737,15 @@ public final class SystemServer implements Dumpable { reportWtf("making Connectivity Service ready", e); } t.traceEnd(); + t.traceBegin("MakeVpnManagerServiceReady"); + try { + if (vpnManagerF != null) { + vpnManagerF.systemReady(); + } + } catch (Throwable e) { + reportWtf("making VpnManagerService ready", e); + } + t.traceEnd(); t.traceBegin("MakeVcnManagementServiceReady"); try { if (vcnManagementF != null) { diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java index 2219d477630e..cbebe6984794 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -38,6 +38,7 @@ import static org.testng.Assert.expectThrows; import android.annotation.UserIdInt; import android.app.Application; +import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; @@ -873,7 +874,8 @@ public class BackupManagerServiceRoboTest { SecurityException.class, () -> backupManagerService.requestBackup( - mUserTwoId, packages, observer, monitor, 0)); + mUserTwoId, packages, observer, monitor, 0, + OperationType.BACKUP)); } /** @@ -891,9 +893,11 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); - backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0, + OperationType.BACKUP); - verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0); + verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0, + OperationType.BACKUP); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -906,9 +910,11 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0); + backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0, + OperationType.BACKUP); - verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0); + verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0, + OperationType.BACKUP); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -921,9 +927,11 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0, + OperationType.BACKUP); - verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0); + verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0, + OperationType.BACKUP); } /** @@ -1084,9 +1092,11 @@ public class BackupManagerServiceRoboTest { registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT); + backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT, + OperationType.BACKUP); - verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT, + OperationType.BACKUP); } /** Test that the backup service does not route methods for non-registered users. */ @@ -1096,9 +1106,11 @@ public class BackupManagerServiceRoboTest { registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT); + backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT, + OperationType.BACKUP); - verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT, + OperationType.BACKUP); } /** Test that the backup service routes methods correctly to the user that requests it. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java new file mode 100644 index 000000000000..195cc010c9a7 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -0,0 +1,725 @@ +/* + * 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.pm; + +import android.apex.ApexSessionInfo; +import android.content.Context; +import android.content.IntentSender; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; +import android.os.SystemProperties; +import android.os.storage.IStorageManager; +import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.content.PackageHelper; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.Preconditions; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertThrows; + +@Presubmit +@RunWith(JUnit4.class) +public class StagingManagerTest { + @Rule + public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + + @Mock private Context mContext; + @Mock private IStorageManager mStorageManager; + @Mock private ApexManager mApexManager; + + private File mTmpDir; + private StagingManager mStagingManager; + + private MockitoSession mMockitoSession; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null); + + mMockitoSession = ExtendedMockito.mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(SystemProperties.class) + .mockStatic(PackageHelper.class) + .startMocking(); + + when(mStorageManager.supportsCheckpoint()).thenReturn(true); + when(mStorageManager.needsCheckpoint()).thenReturn(true); + when(PackageHelper.getStorageManager()).thenReturn(mStorageManager); + + when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true"); + when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true"); + + mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest"); + mStagingManager = new StagingManager(mContext, null, mApexManager); + } + + @After + public void tearDown() throws Exception { + if (mMockitoSession != null) { + mMockitoSession.finishMocking(); + } + } + + /** + * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping + * check. + */ + @Test + public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes() + throws Exception { + // Create 2 sessions with overlapping packages + StagingManager.StagedSession session1 = createSession(111, "com.foo", 1); + StagingManager.StagedSession session2 = createSession(222, "com.foo", 2); + + mStagingManager.createSession(session1); + mStagingManager.createSession(session2); + // Session1 should not fail in spite of the overlapping packages + mStagingManager.checkNonOverlappingWithStagedSessions(session1); + // Session2 should fail due to overlapping packages + assertThrows(PackageManagerException.class, + () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2)); + } + + @Test + public void restoreSessions_nonParentSession_throwsIAE() throws Exception { + FakeStagedSession session = new FakeStagedSession(239); + session.setParentSessionId(1543); + + assertThrows(IllegalArgumentException.class, + () -> mStagingManager.restoreSessions(Arrays.asList(session), false)); + } + + @Test + public void restoreSessions_nonCommittedSession_throwsIAE() throws Exception { + FakeStagedSession session = new FakeStagedSession(239); + + assertThrows(IllegalArgumentException.class, + () -> mStagingManager.restoreSessions(Arrays.asList(session), false)); + } + + @Test + public void restoreSessions_terminalSession_throwsIAE() throws Exception { + FakeStagedSession session = new FakeStagedSession(239); + session.setCommitted(true); + session.setSessionApplied(); + + assertThrows(IllegalArgumentException.class, + () -> mStagingManager.restoreSessions(Arrays.asList(session), false)); + } + + @Test + public void restoreSessions_deviceUpgrading_failsAllSessions() throws Exception { + FakeStagedSession session1 = new FakeStagedSession(37); + session1.setCommitted(true); + FakeStagedSession session2 = new FakeStagedSession(57); + session2.setCommitted(true); + + mStagingManager.restoreSessions(Arrays.asList(session1, session2), true); + + assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed"); + + assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed"); + } + + @Test + public void restoreSessions_multipleSessions_deviceWithoutFsCheckpointSupport_throwISE() + throws Exception { + FakeStagedSession session1 = new FakeStagedSession(37); + session1.setCommitted(true); + FakeStagedSession session2 = new FakeStagedSession(57); + session2.setCommitted(true); + + when(mStorageManager.supportsCheckpoint()).thenReturn(false); + + assertThrows(IllegalStateException.class, + () -> mStagingManager.restoreSessions(Arrays.asList(session1, session2), false)); + } + + @Test + public void restoreSessions_handlesDestroyedAndNotReadySessions() throws Exception { + FakeStagedSession destroyedApkSession = new FakeStagedSession(23); + destroyedApkSession.setCommitted(true); + destroyedApkSession.setDestroyed(true); + + FakeStagedSession destroyedApexSession = new FakeStagedSession(37); + destroyedApexSession.setCommitted(true); + destroyedApexSession.setDestroyed(true); + destroyedApexSession.setIsApex(true); + + FakeStagedSession nonReadyApkSession = new FakeStagedSession(57); + nonReadyApkSession.setCommitted(true); + + FakeStagedSession nonReadyApexSession = new FakeStagedSession(73); + nonReadyApexSession.setCommitted(true); + nonReadyApexSession.setIsApex(true); + + FakeStagedSession destroyedNonReadySession = new FakeStagedSession(101); + destroyedNonReadySession.setCommitted(true); + destroyedNonReadySession.setDestroyed(true); + + FakeStagedSession regularApkSession = new FakeStagedSession(239); + regularApkSession.setCommitted(true); + regularApkSession.setSessionReady(); + + List<StagingManager.StagedSession> sessions = new ArrayList<>(); + sessions.add(destroyedApkSession); + sessions.add(destroyedApexSession); + sessions.add(nonReadyApkSession); + sessions.add(nonReadyApexSession); + sessions.add(destroyedNonReadySession); + sessions.add(regularApkSession); + + mStagingManager.restoreSessions(sessions, false); + + assertThat(sessions).containsExactly(regularApkSession); + assertThat(destroyedApkSession.isDestroyed()).isTrue(); + assertThat(destroyedApexSession.isDestroyed()).isTrue(); + assertThat(destroyedNonReadySession.isDestroyed()).isTrue(); + + mStagingManager.onBootCompletedBroadcastReceived(); + assertThat(nonReadyApkSession.hasPreRebootVerificationStarted()).isTrue(); + assertThat(nonReadyApexSession.hasPreRebootVerificationStarted()).isTrue(); + } + + @Test + public void restoreSessions_unknownApexSession_failsAllSessions() throws Exception { + FakeStagedSession apkSession = new FakeStagedSession(239); + apkSession.setCommitted(true); + apkSession.setSessionReady(); + + FakeStagedSession apexSession = new FakeStagedSession(1543); + apexSession.setCommitted(true); + apexSession.setIsApex(true); + apexSession.setSessionReady(); + + List<StagingManager.StagedSession> sessions = new ArrayList<>(); + sessions.add(apkSession); + sessions.add(apexSession); + + when(mApexManager.getSessions()).thenReturn(new SparseArray<>()); + mStagingManager.restoreSessions(sessions, false); + + // Validate checkpoint wasn't aborted. + verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); + + assertThat(apexSession.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a " + + "staged session supposed to be activated"); + + assertThat(apkSession.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); + } + + @Test + public void restoreSessions_failedApexSessions_failsAllSessions() throws Exception { + FakeStagedSession apkSession = new FakeStagedSession(239); + apkSession.setCommitted(true); + apkSession.setSessionReady(); + + FakeStagedSession apexSession1 = new FakeStagedSession(1543); + apexSession1.setCommitted(true); + apexSession1.setIsApex(true); + apexSession1.setSessionReady(); + + FakeStagedSession apexSession2 = new FakeStagedSession(101); + apexSession2.setCommitted(true); + apexSession2.setIsApex(true); + apexSession2.setSessionReady(); + + FakeStagedSession apexSession3 = new FakeStagedSession(57); + apexSession3.setCommitted(true); + apexSession3.setIsApex(true); + apexSession3.setSessionReady(); + + ApexSessionInfo activationFailed = new ApexSessionInfo(); + activationFailed.sessionId = 1543; + activationFailed.isActivationFailed = true; + + ApexSessionInfo staged = new ApexSessionInfo(); + staged.sessionId = 101; + staged.isStaged = true; + + SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); + apexdSessions.put(1543, activationFailed); + apexdSessions.put(101, staged); + when(mApexManager.getSessions()).thenReturn(apexdSessions); + + List<StagingManager.StagedSession> sessions = new ArrayList<>(); + sessions.add(apkSession); + sessions.add(apexSession1); + sessions.add(apexSession2); + sessions.add(apexSession3); + + mStagingManager.restoreSessions(sessions, false); + + // Validate checkpoint wasn't aborted. + verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); + + assertThat(apexSession1.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. Check logcat " + + "messages from apexd for more information."); + + assertThat(apexSession2.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't " + + "activate nor fail. Marking it as failed anyway."); + + assertThat(apexSession3.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a " + + "staged session supposed to be activated"); + + assertThat(apkSession.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); + } + + @Test + public void restoreSessions_stagedApexSession_failsAllSessions() throws Exception { + FakeStagedSession apkSession = new FakeStagedSession(239); + apkSession.setCommitted(true); + apkSession.setSessionReady(); + + FakeStagedSession apexSession = new FakeStagedSession(1543); + apexSession.setCommitted(true); + apexSession.setIsApex(true); + apexSession.setSessionReady(); + + ApexSessionInfo staged = new ApexSessionInfo(); + staged.sessionId = 1543; + staged.isStaged = true; + + SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); + apexdSessions.put(1543, staged); + when(mApexManager.getSessions()).thenReturn(apexdSessions); + + List<StagingManager.StagedSession> sessions = new ArrayList<>(); + sessions.add(apkSession); + sessions.add(apexSession); + + mStagingManager.restoreSessions(sessions, false); + + // Validate checkpoint wasn't aborted. + verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); + + assertThat(apexSession.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't " + + "activate nor fail. Marking it as failed anyway."); + + assertThat(apkSession.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); + } + + @Test + public void restoreSessions_failedAndActivatedApexSessions_abortsCheckpoint() throws Exception { + FakeStagedSession apkSession = new FakeStagedSession(239); + apkSession.setCommitted(true); + apkSession.setSessionReady(); + + FakeStagedSession apexSession1 = new FakeStagedSession(1543); + apexSession1.setCommitted(true); + apexSession1.setIsApex(true); + apexSession1.setSessionReady(); + + FakeStagedSession apexSession2 = new FakeStagedSession(101); + apexSession2.setCommitted(true); + apexSession2.setIsApex(true); + apexSession2.setSessionReady(); + + FakeStagedSession apexSession3 = new FakeStagedSession(57); + apexSession3.setCommitted(true); + apexSession3.setIsApex(true); + apexSession3.setSessionReady(); + + FakeStagedSession apexSession4 = new FakeStagedSession(37); + apexSession4.setCommitted(true); + apexSession4.setIsApex(true); + apexSession4.setSessionReady(); + + ApexSessionInfo activationFailed = new ApexSessionInfo(); + activationFailed.sessionId = 1543; + activationFailed.isActivationFailed = true; + + ApexSessionInfo activated = new ApexSessionInfo(); + activated.sessionId = 101; + activated.isActivated = true; + + ApexSessionInfo staged = new ApexSessionInfo(); + staged.sessionId = 57; + staged.isActivationFailed = true; + + SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); + apexdSessions.put(1543, activationFailed); + apexdSessions.put(101, activated); + apexdSessions.put(57, staged); + when(mApexManager.getSessions()).thenReturn(apexdSessions); + + List<StagingManager.StagedSession> sessions = new ArrayList<>(); + sessions.add(apkSession); + sessions.add(apexSession1); + sessions.add(apexSession2); + sessions.add(apexSession3); + sessions.add(apexSession4); + + mStagingManager.restoreSessions(sessions, false); + + // Validate checkpoint was aborted. + verify(mStorageManager, times(1)).abortChanges(eq("abort-staged-install"), eq(false)); + } + + @Test + public void restoreSessions_apexSessionInImpossibleState_failsAllSessions() throws Exception { + FakeStagedSession apkSession = new FakeStagedSession(239); + apkSession.setCommitted(true); + apkSession.setSessionReady(); + + FakeStagedSession apexSession = new FakeStagedSession(1543); + apexSession.setCommitted(true); + apexSession.setIsApex(true); + apexSession.setSessionReady(); + + ApexSessionInfo impossible = new ApexSessionInfo(); + impossible.sessionId = 1543; + + SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>(); + apexdSessions.put(1543, impossible); + when(mApexManager.getSessions()).thenReturn(apexdSessions); + + List<StagingManager.StagedSession> sessions = new ArrayList<>(); + sessions.add(apkSession); + sessions.add(apexSession); + + mStagingManager.restoreSessions(sessions, false); + + // Validate checkpoint wasn't aborted. + verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); + + assertThat(apexSession.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state"); + + assertThat(apkSession.getErrorCode()) + .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED); + assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); + } + + private StagingManager.StagedSession createSession(int sessionId, String packageName, + long committedMillis) { + PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + params.isStaged = true; + + InstallSource installSource = InstallSource.create("testInstallInitiator", + "testInstallOriginator", "testInstaller", "testAttributionTag"); + + PackageInstallerSession session = new PackageInstallerSession( + /* callback */ null, + /* context */ null, + /* pm */ null, + /* sessionProvider */ null, + /* looper */ BackgroundThread.getHandler().getLooper(), + /* stagingManager */ null, + /* sessionId */ sessionId, + /* userId */ 456, + /* installerUid */ -1, + /* installSource */ installSource, + /* sessionParams */ params, + /* createdMillis */ 0L, + /* committedMillis */ committedMillis, + /* stageDir */ mTmpDir, + /* stageCid */ null, + /* files */ null, + /* checksums */ null, + /* prepared */ true, + /* committed */ true, + /* destroyed */ false, + /* sealed */ false, // Setting to true would trigger some PM logic. + /* childSessionIds */ null, + /* parentSessionId */ -1, + /* isReady */ false, + /* isFailed */ false, + /* isApplied */false, + /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR, + /* stagedSessionErrorMessage */ "no error"); + + StagingManager.StagedSession stagedSession = spy(session.mStagedSession); + doReturn(packageName).when(stagedSession).getPackageName(); + doAnswer(invocation -> { + Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0); + return filter.test(stagedSession); + }).when(stagedSession).sessionContains(any()); + return stagedSession; + } + + private static final class FakeStagedSession implements StagingManager.StagedSession { + private final int mSessionId; + private boolean mIsApex = false; + private boolean mIsCommitted = false; + private boolean mIsReady = false; + private boolean mIsApplied = false; + private boolean mIsFailed = false; + private @StagedSessionErrorCode int mErrorCode = -1; + private String mErrorMessage; + private boolean mIsDestroyed = false; + private int mParentSessionId = -1; + private String mPackageName; + private boolean mIsAbandonded = false; + private boolean mPreRebootVerificationStarted = false; + private final List<StagingManager.StagedSession> mChildSessions = new ArrayList<>(); + + private FakeStagedSession(int sessionId) { + mSessionId = sessionId; + } + + private void setParentSessionId(int parentSessionId) { + mParentSessionId = parentSessionId; + } + + private void setCommitted(boolean isCommitted) { + mIsCommitted = isCommitted; + } + + private void setIsApex(boolean isApex) { + mIsApex = isApex; + } + + private void setDestroyed(boolean isDestroyed) { + mIsDestroyed = isDestroyed; + } + + private void setPackageName(String packageName) { + mPackageName = packageName; + } + + private boolean isAbandonded() { + return mIsAbandonded; + } + + private boolean hasPreRebootVerificationStarted() { + return mPreRebootVerificationStarted; + } + + private FakeStagedSession addChildSession(FakeStagedSession session) { + mChildSessions.add(session); + session.setParentSessionId(sessionId()); + return this; + } + + private @StagedSessionErrorCode int getErrorCode() { + return mErrorCode; + } + + private String getErrorMessage() { + return mErrorMessage; + } + + @Override + public boolean isMultiPackage() { + return !mChildSessions.isEmpty(); + } + + @Override + public boolean isApexSession() { + return mIsApex; + } + + @Override + public boolean isCommitted() { + return mIsCommitted; + } + + @Override + public boolean isInTerminalState() { + return isSessionApplied() || isSessionFailed(); + } + + @Override + public boolean isDestroyed() { + return mIsDestroyed; + } + + @Override + public boolean isSessionReady() { + return mIsReady; + } + + @Override + public boolean isSessionApplied() { + return mIsApplied; + } + + @Override + public boolean isSessionFailed() { + return mIsFailed; + } + + @Override + public List<StagingManager.StagedSession> getChildSessions() { + return mChildSessions; + } + + @Override + public String getPackageName() { + return mPackageName; + } + + @Override + public int getParentSessionId() { + return mParentSessionId; + } + + @Override + public int sessionId() { + return mSessionId; + } + + @Override + public PackageInstaller.SessionParams sessionParams() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean sessionContains(Predicate<StagingManager.StagedSession> filter) { + return filter.test(this); + } + + @Override + public boolean containsApkSession() { + Preconditions.checkState(!hasParentSessionId(), "Child session"); + if (!isMultiPackage()) { + return !isApexSession(); + } + for (StagingManager.StagedSession session : mChildSessions) { + if (!session.isApexSession()) { + return true; + } + } + return false; + } + + @Override + public boolean containsApexSession() { + Preconditions.checkState(!hasParentSessionId(), "Child session"); + if (!isMultiPackage()) { + return isApexSession(); + } + for (StagingManager.StagedSession session : mChildSessions) { + if (session.isApexSession()) { + return true; + } + } + return false; + } + + @Override + public void setSessionReady() { + mIsReady = true; + } + + @Override + public void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) { + Preconditions.checkState(!mIsApplied, "Already marked as applied"); + mIsFailed = true; + mErrorCode = errorCode; + mErrorMessage = errorMessage; + } + + @Override + public void setSessionApplied() { + Preconditions.checkState(!mIsFailed, "Already marked as failed"); + mIsApplied = true; + } + + @Override + public void installSession(IntentSender statusReceiver) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasParentSessionId() { + return mParentSessionId != -1; + } + + @Override + public long getCommittedMillis() { + throw new UnsupportedOperationException(); + } + + @Override + public void abandon() { + mIsAbandonded = true; + } + + @Override + public boolean notifyStartPreRebootVerification() { + mPreRebootVerificationStarted = true; + // TODO(ioffe): change to true when tests for pre-reboot verification are added. + return false; + } + + @Override + public void notifyEndPreRebootVerification() { + throw new UnsupportedOperationException(); + } + + @Override + public void verifySession() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS index 6561778cb47d..f1402ead3866 100644 --- a/services/tests/servicestests/src/com/android/server/OWNERS +++ b/services/tests/servicestests/src/com/android/server/OWNERS @@ -3,5 +3,4 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS -per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index af11fe125a4e..b98f0257d7b7 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -23,7 +23,6 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import android.app.backup.BackupAgent; import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; @@ -31,7 +30,6 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import com.android.internal.backup.IBackupTransport; import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; @@ -58,7 +56,6 @@ public class UserBackupManagerServiceTest { @Mock IBackupObserver mBackupObserver; @Mock PackageManager mPackageManager; @Mock TransportClient mTransportClient; - @Mock IBackupTransport mBackupTransport; @Mock BackupEligibilityRules mBackupEligibilityRules; @@ -135,29 +132,6 @@ public class UserBackupManagerServiceTest { assertThat(params.mBackupEligibilityRules).isEqualTo(mBackupEligibilityRules); } - @Test - public void testGetOperationTypeFromTransport_returnsMigrationForMigrationTransport() - throws Exception { - when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport); - when(mBackupTransport.getTransportFlags()).thenReturn( - BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER); - - int operationType = mService.getOperationTypeFromTransport(mTransportClient); - - assertThat(operationType).isEqualTo(OperationType.MIGRATION); - } - - @Test - public void testGetOperationTypeFromTransport_returnsBackupByDefault() - throws Exception { - when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport); - when(mBackupTransport.getTransportFlags()).thenReturn(0); - - int operationType = mService.getOperationTypeFromTransport(mTransportClient); - - assertThat(operationType).isEqualTo(OperationType.BACKUP); - } - private static PackageInfo getPackageInfo(String packageName) { PackageInfo packageInfo = new PackageInfo(); packageInfo.applicationInfo = new ApplicationInfo(); diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java index d0767ccb6f87..c165c661a625 100644 --- a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java +++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java @@ -22,6 +22,7 @@ class ApplicationInfoBuilder { private boolean mIsDebuggable; private int mTargetSdk; private String mPackageName; + private long mVersionCode; private ApplicationInfoBuilder() { mTargetSdk = -1; @@ -46,6 +47,11 @@ class ApplicationInfoBuilder { return this; } + ApplicationInfoBuilder withVersionCode(Long versionCode) { + mVersionCode = versionCode; + return this; + } + ApplicationInfo build() { final ApplicationInfo applicationInfo = new ApplicationInfo(); if (mIsDebuggable) { @@ -53,6 +59,7 @@ class ApplicationInfoBuilder { } applicationInfo.packageName = mPackageName; applicationInfo.targetSdkVersion = mTargetSdk; + applicationInfo.longVersionCode = mVersionCode; return applicationInfo; } } diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index a53ff9bc7fdc..8b0e948579fb 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -18,6 +18,7 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -25,6 +26,7 @@ import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.app.compat.ChangeIdStateCache; +import android.app.compat.PackageOverride; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -33,6 +35,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.CompatibilityOverrideConfig; import org.junit.Before; import org.junit.Test; @@ -46,6 +49,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Collections; import java.util.UUID; @RunWith(AndroidJUnit4.class) @@ -83,6 +87,8 @@ public class CompatConfigTest { when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); ChangeIdStateCache.disable(); + when(mPackageManager.getApplicationInfo(anyString(), anyInt())) + .thenThrow(new NameNotFoundException()); } @Test @@ -163,6 +169,10 @@ public class CompatConfigTest { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addDisabledChangeWithId(1234L) .build(); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", true); @@ -177,6 +187,10 @@ public class CompatConfigTest { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addEnabledChangeWithId(1234L) .build(); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", false); @@ -191,6 +205,10 @@ public class CompatConfigTest { CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); compatConfig.forceNonDebuggableFinalForTest(false); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", false); @@ -265,6 +283,71 @@ public class CompatConfigTest { } @Test + public void testOverrideWithAppVersion() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.installed.foo") + .withVersionCode(100L) + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.installed.foo"), anyInt())) + .thenReturn(applicationInfo); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override that doesn't include the installed app version + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, + new PackageOverride.Builder() + .setMaxVersionCode(99L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.installed.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Add override that does include the installed app version + config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, + new PackageOverride.Builder() + .setMinVersionCode(100L) + .setMaxVersionCode(100L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.installed.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + } + + @Test + public void testApplyDeferredOverridesAfterInstallingAppVersion() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.notinstalled.foo") + .withVersionCode(100L) + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenThrow(new NameNotFoundException()); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override before the app is available. + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, new PackageOverride.Builder() + .setMaxVersionCode(99L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Pretend the app is now installed. + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenReturn(applicationInfo); + + compatConfig.recheckOverrides("com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + } + + @Test public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception { ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("com.installedapp.foo") @@ -384,6 +467,8 @@ public class CompatConfigTest { ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("com.some.package") .build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue(); assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); @@ -404,6 +489,8 @@ public class CompatConfigTest { .withPackageName("foo.bar") .withTargetSdk(2) .build(); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isFalse(); assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse(); @@ -425,7 +512,8 @@ public class CompatConfigTest { .withPackageName("foo.bar") .withTargetSdk(2) .build(); - + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.enableTargetSdkChangesForPackage("foo.bar", 3)).isEqualTo(1); assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isTrue(); assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse(); @@ -533,22 +621,114 @@ public class CompatConfigTest { + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + " </override-value>\n" + " </validated>\n" - + " <deferred>\n" - + " </deferred>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + " </change-overrides>\n" + " <change-overrides changeId=\"2\">\n" + " <validated>\n" + " </validated>\n" - + " <deferred>\n" - + " <override-value packageName=\"bar.baz\" enabled=\"false\">\n" - + " </override-value>\n" - + " </deferred>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + "</overrides>\n"); + } + + @Test + public void testSaveOverridesWithRanges() throws Exception { + File overridesFile = new File(createTempDir(), "overrides.xml"); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1L) + .addEnableSinceSdkChangeWithId(2, 2L) + .build(); + compatConfig.forceNonDebuggableFinalForTest(true); + compatConfig.initOverrides(overridesFile); + + compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L, + new PackageOverride.Builder() + .setMinVersionCode(99L) + .setMaxVersionCode(101L) + .setEnabled(true) + .build())), "foo.bar"); + + assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"99\" maxVersionCode=\"101\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + " </change-overrides>\n" + "</overrides>\n"); } @Test - public void testLoadOverrides() throws Exception { + public void testLoadOverridesRaw() throws Exception { + File tempDir = createTempDir(); + File overridesFile = new File(tempDir, "overrides.xml"); + // Change 1 is enabled for foo.bar (validated) + // Change 2 is disabled for bar.baz (deferred) + String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + + " </override-value>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + " <change-overrides changeId=\"2\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + "</overrides>\n"; + writeToFile(tempDir, "overrides.xml", xmlData); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1L) + .addEnableSinceSdkChangeWithId(2, 2L) + .build(); + compatConfig.forceNonDebuggableFinalForTest(true); + compatConfig.initOverrides(overridesFile); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("foo.bar") + .withVersionCode(100L) + .debuggable() + .build(); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); + when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) + .thenThrow(new NameNotFoundException()); + + assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue(); + assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse(); + + compatConfig.recheckOverrides("foo.bar"); + assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue(); + } + + @Test + public void testLoadOverridesDeferred() throws Exception { File tempDir = createTempDir(); File overridesFile = new File(tempDir, "overrides.xml"); // Change 1 is enabled for foo.bar (validated) diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index a1b2dc8bd82d..799b06734b54 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -196,6 +196,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -208,6 +211,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -219,6 +225,9 @@ public class PlatformCompatTest { public void testListenerCalledOnSetOverridesTwoListeners() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -244,6 +253,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -252,9 +264,12 @@ public class PlatformCompatTest { } @Test - public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception { + public void testListenerCalledOnSetOverridesForTestTwoListeners() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -280,6 +295,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); @@ -299,6 +317,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -318,6 +339,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); @@ -336,6 +360,9 @@ public class PlatformCompatTest { public void testListenerCalledOnClearOverrideDoesntExist() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.clearOverride(1, PACKAGE_NAME); // Listener not called when a non existing override is removed. verify(mListener1, never()).onCompatChange(PACKAGE_NAME); 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 7597cbf322f5..4002f5b4939e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -4329,6 +4329,68 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + public void testSetNetworkLoggingEnabled_asPo() throws Exception { + final int managedProfileUserId = CALLER_USER_HANDLE; + final int managedProfileAdminUid = + UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); + mContext.binder.callingUid = managedProfileAdminUid; + mContext.applicationInfo = new ApplicationInfo(); + mContext.packageName = admin1.getPackageName(); + addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.S); + when(getServices().iipConnectivityMetrics + .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true); + + // Check no logs have been retrieved so far. + assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1); + + // Enable network logging + dpm.setNetworkLoggingEnabled(admin1, true); + assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1); + + // Retrieve the network logs and verify timestamp has been updated. + final long beforeRetrieval = System.currentTimeMillis(); + + dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */); + + final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime(); + final long afterRetrieval = System.currentTimeMillis(); + assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue(); + assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue(); + } + + @Test + public void testSetNetworkLoggingEnabled_asPoOfOrgOwnedDevice() throws Exception { + // Setup profile owner on organization-owned device + final int MANAGED_PROFILE_ADMIN_UID = + UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID); + addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); + configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE); + + mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; + mContext.packageName = admin1.getPackageName(); + mContext.applicationInfo = new ApplicationInfo(); + when(getServices().iipConnectivityMetrics + .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true); + + // Check no logs have been retrieved so far. + assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1); + + // Enable network logging + dpm.setNetworkLoggingEnabled(admin1, true); + assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1); + + // Retrieve the network logs and verify timestamp has been updated. + final long beforeRetrieval = System.currentTimeMillis(); + + dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */); + + final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime(); + final long afterRetrieval = System.currentTimeMillis(); + assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue(); + assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue(); + } + + @Test public void testGetBindDeviceAdminTargetUsers() throws Exception { mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java index 7506dd45ad82..743b25f5c2b4 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java @@ -125,7 +125,7 @@ public class NetworkEventTest extends DpmTestBase { private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception { // GIVEN a handler with events NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(), - mDpmTestable, startingId); + mDpmTestable, startingId, DpmMockContext.CALLER_USER_HANDLE); // GIVEN network events are sent to the handler. for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) { ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo", diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 1a2266139405..a078a77b4498 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -52,7 +52,8 @@ import javax.annotation.Nullable; public final class DeviceStateManagerServiceTest { private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT"); private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER"); - private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(999, "UNSUPPORTED"); + // A device state that is not reported as being supported for the default test provider. + private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED"); private TestDeviceStatePolicy mPolicy; private TestDeviceStateProvider mProvider; diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java new file mode 100644 index 000000000000..b5c8053ad77e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.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.devicestate; + +import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertThrows; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for {@link DeviceState}. + * <p/> + * Run with <code>atest DeviceStateTest</code>. + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public final class DeviceStateTest { + @Test + public void testConstruct() { + final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */, + "CLOSED" /* name */); + assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE); + assertEquals(state.getName(), "CLOSED"); + } + + @Test + public void testConstruct_nullName() { + final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */, + null /* name */); + assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE); + assertNull(state.getName()); + } + + @Test + public void testConstruct_tooLargeIdentifier() { + assertThrows(IllegalArgumentException.class, () -> { + final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */, + null /* name */); + }); + } + + @Test + public void testConstruct_tooSmallIdentifier() { + assertThrows(IllegalArgumentException.class, () -> { + final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */, + null /* name */); + }); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 23a4c2f417c5..732c08c98f68 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -30,6 +30,7 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; +import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -43,6 +44,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest +@Presubmit @RunWith(AndroidJUnit4.class) public class AutomaticBrightnessControllerTest { private static final float BRIGHTNESS_MIN_FLOAT = 0.0f; diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index f0b4f1bec77b..9396ed256409 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -30,6 +30,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.BrightnessConfiguration; import android.os.PowerManager; +import android.platform.test.annotations.Presubmit; import android.util.MathUtils; import android.util.Spline; @@ -42,6 +43,7 @@ import org.junit.runner.RunWith; import java.util.Arrays; @SmallTest +@Presubmit @RunWith(AndroidJUnit4.class) public class BrightnessMappingStrategyTest { diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 640d6e599736..1c55072cab2c 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -45,7 +45,9 @@ import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.InputManagerInternal; import android.os.Handler; import android.os.IBinder; +import android.os.MessageQueue; import android.os.Process; +import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; @@ -76,10 +78,15 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.time.Duration; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.stream.LongStream; @SmallTest +@Presubmit @RunWith(AndroidJUnit4.class) public class DisplayManagerServiceTest { private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1; @@ -193,8 +200,8 @@ public class DisplayManagerServiceTest { verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); List<DisplayViewport> viewports = viewportCaptor.getValue(); - // Expect to receive 2 viewports: internal, and virtual - assertEquals(2, viewports.size()); + // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual + assertTrue(viewports.size() >= 2); DisplayViewport virtualViewport = null; DisplayViewport internalViewport = null; @@ -202,7 +209,10 @@ public class DisplayManagerServiceTest { DisplayViewport v = viewports.get(i); switch (v.type) { case DisplayViewport.VIEWPORT_INTERNAL: { + // If more than one internal viewport, this will get overwritten several times, + // which for the purposes of this test is fine. internalViewport = v; + assertTrue(internalViewport.valid); break; } case DisplayViewport.VIEWPORT_EXTERNAL: { @@ -219,9 +229,6 @@ public class DisplayManagerServiceTest { assertNotNull(internalViewport); assertNotNull(virtualViewport); - // INTERNAL - assertTrue(internalViewport.valid); - // VIRTUAL assertEquals(height, virtualViewport.deviceHeight); assertEquals(width, virtualViewport.deviceWidth); @@ -243,10 +250,12 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); final int displayIds[] = bs.getDisplayIds(); - assertEquals(1, displayIds.length); - final int displayId = displayIds[0]; - DisplayInfo info = bs.getDisplayInfo(displayId); - assertEquals(info.type, Display.TYPE_INTERNAL); + final int size = displayIds.length; + assertTrue(size > 0); + for (int i = 0; i < size; i++) { + DisplayInfo info = bs.getDisplayInfo(displayIds[i]); + assertEquals(info.type, Display.TYPE_INTERNAL); + } displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -257,16 +266,22 @@ public class DisplayManagerServiceTest { verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); List<DisplayViewport> viewports = viewportCaptor.getValue(); - // Expect to receive actual viewports: 1 internal - assertEquals(1, viewports.size()); - - DisplayViewport internalViewport = viewports.get(0); - - // INTERNAL is the only one actual display. - assertNotNull(internalViewport); - assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type); - assertTrue(internalViewport.valid); - assertEquals(displayId, internalViewport.displayId); + // Due to the nature of foldables, we may have a different number of viewports than + // displays, just verify there's at least one. + final int viewportSize = viewports.size(); + assertTrue(viewportSize > 0); + + // Now verify that each viewport's displayId is valid. + Arrays.sort(displayIds); + for (int i = 0; i < viewportSize; i++) { + DisplayViewport internalViewport = viewports.get(i); + + // INTERNAL is the only one actual display. + assertNotNull(internalViewport); + assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type); + assertTrue(internalViewport.valid); + assertTrue(Arrays.binarySearch(displayIds, internalViewport.displayId) >= 0); + } } @Test @@ -486,7 +501,6 @@ public class DisplayManagerServiceTest { * Tests that collection of display color sampling results are sensible. */ @Test - @FlakyTest(bugId = 172555744) public void testDisplayedContentSampling() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); @@ -937,8 +951,22 @@ public class DisplayManagerServiceTest { // Would prefer to call displayManager.onStart() directly here but it performs binderService // registration which triggers security exceptions when running from a test. handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS); - // flush the handler - handler.runWithScissors(() -> {}, 0 /* now */); + waitForIdleHandler(handler, Duration.ofSeconds(1)); + } + + private void waitForIdleHandler(Handler handler, Duration timeout) { + final MessageQueue queue = handler.getLooper().getQueue(); + final CountDownLatch latch = new CountDownLatch(1); + queue.addIdleHandler(() -> { + latch.countDown(); + // Remove idle handler + return false; + }); + try { + latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("Interrupted unexpectedly: " + e); + } } private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub { diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 2e3178b13892..ee0f2e8c6f6a 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -41,12 +41,13 @@ import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.database.ContentObserver; -import android.hardware.display.DisplayManager; import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; +import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.Settings; import android.test.mock.MockContentResolver; @@ -82,6 +83,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @SmallTest +@Presubmit @RunWith(AndroidJUnit4.class) public class DisplayModeDirectorTest { // The tolerance within which we consider something approximately equals. @@ -588,6 +590,12 @@ public class DisplayModeDirectorTest { @Test public void testSensorRegistration() { + // First, configure brightness zones or DMD won't register for sensor data. + final FakeDeviceConfig config = mInjector.getDeviceConfig(); + config.setRefreshRateInHighZone(60); + config.setHighDisplayBrightnessThresholds(new int[] { 255 }); + config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); + DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); setPeakRefreshRate(90 /*fps*/); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index ac4501723c90..ece0a627f051 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -20,17 +20,23 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.app.PropertyInvalidatedCache; import android.graphics.Point; +import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; +import androidx.test.filters.SmallTest; + import org.junit.Before; import org.junit.Test; import java.io.InputStream; import java.io.OutputStream; +@SmallTest +@Presubmit public class LogicalDisplayTest { private static final int DISPLAY_ID = 0; private static final int LAYER_STACK = 0; @@ -52,6 +58,9 @@ public class LogicalDisplayTest { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo); + // Disable binder caches in this process. + PropertyInvalidatedCache.disableForTestMode(); + DisplayDeviceRepository repo = new DisplayDeviceRepository( new DisplayManagerService.SyncRoot(), new PersistentDataStore(new PersistentDataStore.Injector() { diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java index 196454bd32ce..d72606ac44e5 100644 --- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.hardware.display.BrightnessConfiguration; +import android.platform.test.annotations.Presubmit; import android.util.Pair; import androidx.test.filters.SmallTest; @@ -40,6 +41,7 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; @SmallTest +@Presubmit @RunWith(AndroidJUnit4.class) public class PersistentDataStoreTest { private PersistentDataStore mDataStore; 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 9f0d9829df01..5342486f930b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -15,6 +15,7 @@ */ package com.android.server.hdmi; +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_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_INVALID; @@ -49,6 +50,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.concurrent.TimeUnit; @SmallTest @Presubmit @@ -1586,4 +1588,46 @@ public class HdmiCecLocalDevicePlaybackTest { assertThat(features.contains( Constants.RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU)).isFalse(); } + + @Test + public void doesNotSupportRecordTvScreen() { + HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress, + Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM); + + mNativeWrapper.onCecMessage(recordTvScreen); + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_RECORD_TV_SCREEN, + ABORT_UNRECOGNIZED_OPCODE); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); + } + + @Test + public void shouldHandleUserControlPressedAndReleased() { + HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( + ADDR_TV, mPlaybackLogicalAddress, + HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP); + HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased( + ADDR_TV, mPlaybackLogicalAddress); + + mNativeWrapper.onCecMessage(userControlPressed); + mTestLooper.dispatchAll(); + + // Move past the follower safety timeout + mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(2)); + mTestLooper.dispatchAll(); + + mNativeWrapper.onCecMessage(userControlReleased); + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbortPressed = HdmiCecMessageBuilder.buildFeatureAbortCommand( + mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_PRESSED, + ABORT_UNRECOGNIZED_OPCODE); + HdmiCecMessage featureAbortReleased = HdmiCecMessageBuilder.buildFeatureAbortCommand( + mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_RELEASED, + ABORT_UNRECOGNIZED_OPCODE); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased); + } } 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 0f527f3713b2..4623eb5b7d4b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -15,8 +15,11 @@ */ package com.android.server.hdmi; +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_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; +import static com.android.server.hdmi.Constants.ADDR_RECORDER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; @@ -27,6 +30,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.media.AudioManager; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; @@ -66,6 +70,7 @@ public class HdmiCecLocalDeviceTvTest { private IPowerManager mIPowerManagerMock; @Mock private IThermalService mIThermalServiceMock; + @Mock private AudioManager mAudioManager; @Before public void setUp() { @@ -101,11 +106,21 @@ public class HdmiCecLocalDeviceTvTest { } @Override + boolean isPowerStandby() { + return false; + } + + @Override protected PowerManager getPowerManager() { return powerManager; } @Override + AudioManager getAudioManager() { + return mAudioManager; + } + + @Override protected HdmiCecConfig getHdmiCecConfig() { return hdmiCecConfig; } @@ -121,9 +136,11 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); - HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false); + hdmiPortInfos[1] = + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); @@ -302,4 +319,185 @@ public class HdmiCecLocalDeviceTvTest { assertThat(features.contains(Constants.RC_PROFILE_TV_THREE)).isFalse(); assertThat(features.contains(Constants.RC_PROFILE_TV_FOUR)).isFalse(); } + + @Test + public void startArcAction_enable_noAudioDevice() { + mHdmiCecLocalDeviceTv.startArcAction(true); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + } + + + @Test + public void startArcAction_disable_noAudioDevice() { + mHdmiCecLocalDeviceTv.startArcAction(false); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + } + + @Test + public void startArcAction_enable_portDoesNotSupportArc() { + // Emulate Audio device on port 0x1000 (does not support ARC) + mNativeWrapper.setPortConnectionStatus(1, true); + HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(hdmiCecMessage); + + mHdmiCecLocalDeviceTv.startArcAction(true); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + } + + @Test + public void startArcAction_disable_portDoesNotSupportArc() { + // Emulate Audio device on port 0x1000 (does not support ARC) + mNativeWrapper.setPortConnectionStatus(1, true); + HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(hdmiCecMessage); + + mHdmiCecLocalDeviceTv.startArcAction(false); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + } + + @Test + public void startArcAction_enable_portSupportsArc() { + // Emulate Audio device on port 0x2000 (supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(hdmiCecMessage); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.startArcAction(true); + mTestLooper.dispatchAll(); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + } + + @Test + public void startArcAction_disable_portSupportsArc() { + // Emulate Audio device on port 0x2000 (supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(hdmiCecMessage); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.startArcAction(false); + mTestLooper.dispatchAll(); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + } + + @Test + public void handleInitiateArc_noAudioDevice() { + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(requestArcInitiation); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); + } + + @Test + public void handleInitiateArc_portDoesNotSupportArc() { + // Emulate Audio device on port 0x1000 (does not support ARC) + mNativeWrapper.setPortConnectionStatus(1, true); + HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(hdmiCecMessage); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(requestArcInitiation); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); + } + + @Test + public void handleInitiateArc_portSupportsArc() { + // Emulate Audio device on port 0x2000 (supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(hdmiCecMessage); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(requestArcInitiation); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); + } + + @Test + public void supportsRecordTvScreen() { + HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_RECORDER_1, mTvLogicalAddress, + Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM); + + mNativeWrapper.onCecMessage(recordTvScreen); + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + mTvLogicalAddress, ADDR_RECORDER_1, Constants.MESSAGE_RECORD_TV_SCREEN, + ABORT_UNRECOGNIZED_OPCODE); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java new file mode 100644 index 000000000000..b8dfd5672056 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -0,0 +1,280 @@ +/* + * 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.hdmi; + +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_TV; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.media.AudioManager; +import android.os.Handler; +import android.os.IPowerManager; +import android.os.IThermalService; +import android.os.Looper; +import android.os.PowerManager; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +/** Tests for {@link ActiveSourceAction} */ +@SmallTest +@RunWith(JUnit4.class) +public class PowerStatusMonitorActionTest { + + private Context mContextSpy; + private HdmiControlService mHdmiControlService; + private FakeNativeWrapper mNativeWrapper; + + private TestLooper mTestLooper = new TestLooper(); + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPhysicalAddress; + private HdmiCecLocalDeviceTv mTvDevice; + + @Mock + private IPowerManager mIPowerManagerMock; + @Mock + private IThermalService mIThermalServiceMock; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + + PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, + mIThermalServiceMock, new Handler(mTestLooper.getLooper())); + when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); + when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); + when(mIPowerManagerMock.isInteractive()).thenReturn(true); + + HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); + + mHdmiControlService = new HdmiControlService(mContextSpy) { + @Override + AudioManager getAudioManager() { + return new AudioManager() { + @Override + public void setWiredDeviceConnectionState( + int type, int state, String address, String name) { + // Do nothing. + } + }; + } + + @Override + void wakeUp() { + } + + @Override + boolean isPowerStandby() { + return false; + } + + @Override + protected PowerManager getPowerManager() { + return powerManager; + } + + @Override + protected void writeStringSystemProperty(String key, String value) { + // do nothing + } + + @Override + protected HdmiCecConfig getHdmiCecConfig() { + return hdmiCecConfig; + } + }; + + Looper looper = mTestLooper.getLooper(); + mHdmiControlService.setIoLooper(looper); + mNativeWrapper = new FakeNativeWrapper(); + HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( + this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); + mHdmiControlService.setCecController(hdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService); + mTvDevice.init(); + mLocalDevices.add(mTvDevice); + mTestLooper.dispatchAll(); + HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2]; + hdmiPortInfo[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false); + hdmiPortInfo[1] = + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfo); + mHdmiControlService.initService(); + mPhysicalAddress = 0x0000; + mNativeWrapper.setPhysicalAddress(mPhysicalAddress); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + } + + @Test + public void sourceDevice_1_4_updatesPowerState() { + sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000); + + PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice); + action.start(); + assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + ADDR_TV, + ADDR_PLAYBACK_1); + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); + + reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_ON); + assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON); + + mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60)); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); + + reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_STANDBY); + assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_STANDBY); + } + + private void assertPowerStatus(int logicalAddress, int powerStatus) { + HdmiDeviceInfo deviceInfo = mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo( + logicalAddress); + assertThat(deviceInfo).isNotNull(); + assertThat(deviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); + } + + @Test + public void sourceDevice_2_0_doesNotUpdatePowerState() { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.HDMI_CEC_VERSION_2_0); + sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000); + reportPowerStatus(ADDR_PLAYBACK_1, true, HdmiControlManager.POWER_STATUS_ON); + mTestLooper.dispatchAll(); + + PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice); + action.start(); + + assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + ADDR_TV, + ADDR_PLAYBACK_1); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); + + mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60)); + mTestLooper.dispatchAll(); + + assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON); + } + + @Test + public void mixedSourceDevices_localDevice_1_4_updatesAll() { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + mTestLooper.dispatchAll(); + sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000); + sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000); + reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON); + + assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN); + assertPowerStatus(ADDR_PLAYBACK_2, HdmiControlManager.POWER_STATUS_ON); + + PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice); + action.start(); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + ADDR_TV, + ADDR_PLAYBACK_1); + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); + + HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + ADDR_TV, + ADDR_PLAYBACK_2); + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus2); + } + + @Test + public void mixedSourceDevices_localDevice_2_0_onlyUpdates_1_4() { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.HDMI_CEC_VERSION_2_0); + mTestLooper.dispatchAll(); + sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000); + sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000); + reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON); + + PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice); + action.start(); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + ADDR_TV, + ADDR_PLAYBACK_1); + + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); + + HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + ADDR_TV, + ADDR_PLAYBACK_2); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus2); + } + + private void sendMessageFromPlaybackDevice(int logicalAddress, int physicalAddress) { + HdmiCecMessage playbackDevice = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + logicalAddress, physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK); + mNativeWrapper.onCecMessage(playbackDevice); + mTestLooper.dispatchAll(); + } + + private void reportPowerStatus(int logicalAddress, boolean broadcast, int powerStatus) { + int destination = broadcast ? ADDR_BROADCAST : ADDR_TV; + HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( + logicalAddress, destination, + powerStatus); + mNativeWrapper.onCecMessage(reportPowerStatus); + mTestLooper.dispatchAll(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java index 353ac4bd2129..f9b25d9342d3 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java @@ -39,6 +39,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -51,7 +52,7 @@ public class WorkCountTrackerTest { private static final String TAG = "WorkerCountTrackerTest"; private static final double[] EQUAL_PROBABILITY_CDF = - buildCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, + buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES); private Random mRandom; @@ -64,18 +65,19 @@ public class WorkCountTrackerTest { } @NonNull - private static double[] buildCdf(double pTop, double pEj, double pBg, double pBgUser) { - double[] cdf = new double[JobConcurrencyManager.NUM_WORK_TYPES]; + private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) { + return buildCdf(pTop, pEj, pBg, pBgUser); + } + + @NonNull + private static double[] buildCdf(double... probs) { + double[] cdf = new double[probs.length]; double sum = 0; - sum += pTop; - cdf[0] = sum; - sum += pEj; - cdf[1] = sum; - sum += pBg; - cdf[2] = sum; - sum += pBgUser; - cdf[3] = sum; + for (int i = 0; i < probs.length; ++i) { + sum += probs[i]; + cdf[i] = sum; + } if (Double.compare(1, sum) != 0) { throw new IllegalArgumentException("probabilities don't sum to one: " + sum); @@ -83,25 +85,30 @@ public class WorkCountTrackerTest { return cdf; } - @JobConcurrencyManager.WorkType - static int getRandomWorkType(double[] cdf, double rand) { + static int getRandomIndex(double[] cdf, double rand) { for (int i = cdf.length - 1; i >= 0; --i) { if (rand < cdf[i] && (i == 0 || rand > cdf[i - 1])) { - switch (i) { - case 0: - return WORK_TYPE_TOP; - case 1: - return WORK_TYPE_EJ; - case 2: - return WORK_TYPE_BG; - case 3: - return WORK_TYPE_BGUSER; - default: - throw new IllegalStateException("Unknown work type"); - } + return i; } } - throw new IllegalStateException("Couldn't pick random work type"); + throw new IllegalStateException("Couldn't pick random index"); + } + + @JobConcurrencyManager.WorkType + static int getRandomWorkType(double[] cdf, double rand) { + final int index = getRandomIndex(cdf, rand); + switch (index) { + case 0: + return WORK_TYPE_TOP; + case 1: + return WORK_TYPE_EJ; + case 2: + return WORK_TYPE_BG; + case 3: + return WORK_TYPE_BGUSER; + default: + throw new IllegalStateException("Unknown work type"); + } } /** @@ -110,25 +117,59 @@ public class WorkCountTrackerTest { class Jobs { public final SparseIntArray running = new SparseIntArray(); public final SparseIntArray pending = new SparseIntArray(); + public final List<Integer> pendingMultiTypes = new ArrayList<>(); + + /** + * @param probStart Probability of starting a job + * @param typeCdf The CDF representing the probability of each work type + * @param numTypesCdf The CDF representing the probability of a job having X different + * work types. Each index i represents i+1 work types (ie. index 0 = 1 + * work type, index 3 = 4 work types). + */ + public void maybeEnqueueJobs(double probStart, double[] typeCdf, double[] numTypesCdf) { + assertThat(numTypesCdf.length).isAtMost(NUM_WORK_TYPES); + assertThat(numTypesCdf.length).isAtLeast(1); - public void maybeEnqueueJobs(double probStart, double[] typeCdf) { while (mRandom.nextDouble() < probStart) { - final int workType = getRandomWorkType(typeCdf, mRandom.nextDouble()); - pending.put(workType, pending.get(workType) + 1); + final int numTypes = getRandomIndex(numTypesCdf, mRandom.nextDouble()) + 1; + int types = WORK_TYPE_NONE; + for (int i = 0; i < numTypes; ++i) { + types |= getRandomWorkType(typeCdf, mRandom.nextDouble()); + } + addPending(types, 1); } } - public void maybeFinishJobs(double probStop) { - for (int i = running.get(WORK_TYPE_BG); i > 0; i--) { - if (mRandom.nextDouble() < probStop) { - running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1); - mWorkCountTracker.onJobFinished(WORK_TYPE_BG); + void addPending(int allWorkTypes, int num) { + for (int n = 0; n < num; ++n) { + for (int i = 0; i < 32; ++i) { + final int type = 1 << i; + if ((allWorkTypes & type) != 0) { + pending.put(type, pending.get(type) + 1); + } } + pendingMultiTypes.add(allWorkTypes); } - for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) { - if (mRandom.nextDouble() < probStop) { - running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1); - mWorkCountTracker.onJobFinished(WORK_TYPE_TOP); + } + + void removePending(int allWorkTypes) { + for (int i = 0; i < 32; ++i) { + final int type = 1 << i; + if ((allWorkTypes & type) != 0) { + pending.put(type, pending.get(type) - 1); + } + } + pendingMultiTypes.remove(Integer.valueOf(allWorkTypes)); + } + + public void maybeFinishJobs(double probStop) { + for (int i = running.size() - 1; i >= 0; --i) { + final int workType = running.keyAt(i); + for (int c = running.valueAt(i); c > 0; --c) { + if (mRandom.nextDouble() < probStop) { + running.put(workType, running.get(workType) - 1); + mWorkCountTracker.onJobFinished(workType); + } } } } @@ -171,6 +212,15 @@ public class WorkCountTrackerTest { return false; } + private int getPendingMultiType(Jobs jobs, @JobConcurrencyManager.WorkType int workType) { + for (int multiType : jobs.pendingMultiTypes) { + if ((multiType & workType) != 0) { + return multiType; + } + } + throw new IllegalStateException("No pending multi type with work type: " + workType); + } + private void startPendingJobs(Jobs jobs) { while (hasStartablePendingJob(jobs)) { final int startingWorkType = @@ -178,9 +228,10 @@ public class WorkCountTrackerTest { if (jobs.pending.get(startingWorkType) > 0 && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) { - jobs.pending.put(startingWorkType, jobs.pending.get(startingWorkType) - 1); + final int pendingMultiType = getPendingMultiType(jobs, startingWorkType); + jobs.removePending(pendingMultiType); jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1); - mWorkCountTracker.stageJob(startingWorkType); + mWorkCountTracker.stageJob(startingWorkType, pendingMultiType); mWorkCountTracker.onJobStarted(startingWorkType); } } @@ -192,12 +243,17 @@ public class WorkCountTrackerTest { private void checkRandom(Jobs jobs, int numTests, int totalMax, @NonNull List<Pair<Integer, Integer>> minLimits, @NonNull List<Pair<Integer, Integer>> maxLimits, - double probStart, double[] typeCdf, double probStop) { + double probStart, double[] typeCdf, double[] numTypesCdf, double probStop) { + int minExpected = 0; + for (Pair<Integer, Integer> minLimit : minLimits) { + minExpected = Math.min(minLimit.second, minExpected); + } for (int i = 0; i < numTests; i++) { jobs.maybeFinishJobs(probStop); - jobs.maybeEnqueueJobs(probStart, typeCdf); + jobs.maybeEnqueueJobs(probStart, typeCdf, numTypesCdf); recount(jobs, totalMax, minLimits, maxLimits); + final int numPending = jobs.pendingMultiTypes.size(); startPendingJobs(jobs); int totalRunning = 0; @@ -209,6 +265,7 @@ public class WorkCountTrackerTest { totalRunning += numRunning; } assertThat(totalRunning).isAtMost(totalMax); + assertThat(totalRunning).isAtLeast(Math.min(minExpected, numPending)); for (Pair<Integer, Integer> maxLimit : maxLimits) { assertWithMessage("Work type " + maxLimit.first + " is running too many jobs") .that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second); @@ -233,7 +290,7 @@ public class WorkCountTrackerTest { final double probStart = 0.1; checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, - EQUAL_PROBABILITY_CDF, probStop); + EQUAL_PROBABILITY_CDF, EQUAL_PROBABILITY_CDF, probStop); } @Test @@ -246,10 +303,12 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildCdf(0.5, 0, 0.5, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0); + final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -262,10 +321,12 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] numTypesCdf = buildCdf(.75, .2, .05); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -278,10 +339,12 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List<Pair<Integer, Integer>> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] numTypesCdf = buildCdf(.05, .95); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -294,10 +357,12 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildCdf(0.1, 0, 0.8, .1); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1); + final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -310,10 +375,12 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildCdf(0.9, 0, 0.1, 0); + final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0); + final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -326,10 +393,12 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildCdf(0.1, 0, 0.1, .8); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8); + final double[] numTypesCdf = buildCdf(0.5, 0.5); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -343,10 +412,12 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.4; - final double[] cdf = buildCdf(0.9, 0, 0.05, 0.05); + final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05); + final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -360,10 +431,12 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildCdf(0, 0, 0.5, 0.5); + final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5); + final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -377,10 +450,12 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildCdf(0, 0, 0.1, 0.9); + final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9); + final double[] numTypesCdf = buildCdf(0.9, 0.1); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -394,10 +469,12 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildCdf(0, 0, 0.9, 0.1); + final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1); + final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -410,10 +487,12 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildCdf(0.5, 0.5, 0, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0); + final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -430,10 +509,11 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)); final double probStop = 0.01; + final double[] numTypesCdf = buildCdf(0, 0.05, 0.05, 0.9); final double probStart = 0.99; checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, - EQUAL_PROBABILITY_CDF, probStop); + EQUAL_PROBABILITY_CDF, numTypesCdf, probStop); } @Test @@ -446,10 +526,12 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildCdf(.1, 0.5, 0.35, 0.05); + final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05); + final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } @Test @@ -464,10 +546,12 @@ public class WorkCountTrackerTest { final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildCdf(0.01, 0.49, 0.1, 0.4); + final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4); + final double[] numTypesCdf = buildCdf(0.7, 0.3); final double probStart = 0.5; - checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop); + checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, + cdf, numTypesCdf, probStop); } /** Used by the following tests */ @@ -483,7 +567,7 @@ public class WorkCountTrackerTest { jobs.running.put(run.first, run.second); } for (Pair<Integer, Integer> pend : pending) { - jobs.pending.put(pend.first, pend.second); + jobs.addPending(pend.first, pend.second); } recount(jobs, totalMax, minLimits, maxLimits); @@ -651,14 +735,32 @@ public class WorkCountTrackerTest { Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)), /* resPen */ List.of( Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2))); + + // Test multi-types + checkSimple(6, + /* min */ List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)), + /* max */ List.of( + Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)), + /* run */ List.of(), + /* pen */ List.of( + // 2 of these as TOP, 1 as EJ + Pair.create(WORK_TYPE_TOP | WORK_TYPE_EJ, 3), + // 1 as EJ, 2 as BG + Pair.create(WORK_TYPE_EJ | WORK_TYPE_BG, 3), + Pair.create(WORK_TYPE_BG, 4), + Pair.create(WORK_TYPE_BGUSER, 1)), + /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), + Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)), + /* resPen */ List.of( + Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1))); } /** Tests that the counter updates properly when jobs are stopped. */ @Test public void testJobLifecycleLoop() { final Jobs jobs = new Jobs(); - jobs.pending.put(WORK_TYPE_TOP, 11); - jobs.pending.put(WORK_TYPE_BG, 10); + jobs.addPending(WORK_TYPE_TOP, 11); + jobs.addPending(WORK_TYPE_BG, 10); final int totalMax = 6; final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 1)); @@ -729,4 +831,149 @@ public class WorkCountTrackerTest { assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(3); } + + /** Tests that the counter updates properly when jobs are stopped. */ + @Test + public void testJobLifecycleLoop_Multitype() { + final Jobs jobs = new Jobs(); + jobs.addPending(WORK_TYPE_TOP, 6); // a + jobs.addPending(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b + jobs.addPending(WORK_TYPE_BG, 10); // c + + final int totalMax = 8; + final List<Pair<Integer, Integer>> minLimits = + List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)); + final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5)); + + recount(jobs, totalMax, minLimits, maxLimits); + + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(5); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10); + + startPendingJobs(jobs); + + assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6); + assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1); + assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4); + // Since starting happens in random order, all EJs could have run first. + assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9); + + // Stop all jobs + jobs.maybeFinishJobs(1); + + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP); + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ); + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG); + + startPendingJobs(jobs); + + assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4); + // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot + // for EJ, which would reduce BG count to 3 instead of 4. + assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3); + assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4); + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(5); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(6); + + // Stop only a bg job and make sure the counter only allows another bg job to start. + jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1); + mWorkCountTracker.onJobFinished(WORK_TYPE_BG); + + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE); + // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot + // for EJ, which would reduce BG count to 3 instead of 4. + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG); + + startPendingJobs(jobs); + + assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4); + assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3); + assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4); + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(4); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(5); + } + + /** Tests that the counter updates properly when jobs are stopped. */ + @Test + public void testJobLifecycleLoop_Multitype_RandomOrder() { + final Jobs jobs = new Jobs(); + SparseIntArray multiToCount = new SparseIntArray(); + multiToCount.put(WORK_TYPE_TOP, 6); // a + multiToCount.put(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b + multiToCount.put(WORK_TYPE_EJ | WORK_TYPE_BG, 5); // c + multiToCount.put(WORK_TYPE_BG, 5); // d + while (multiToCount.size() > 0) { + final int index = mRandom.nextInt(multiToCount.size()); + final int count = multiToCount.valueAt(index); + jobs.addPending(multiToCount.keyAt(index), 1); + if (count <= 1) { + multiToCount.removeAt(index); + } else { + multiToCount.put(multiToCount.keyAt(index), count - 1); + } + } + + final int totalMax = 8; + final List<Pair<Integer, Integer>> minLimits = + List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)); + final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5)); + + recount(jobs, totalMax, minLimits, maxLimits); + + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(10); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10); + + startPendingJobs(jobs); + + // Random order, but we should have 6 TOP, 1 EJ, and 1 BG running. + assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6); + assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1); + assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4); + // Can't equate pending EJ since some could be running as TOP and BG + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9); + + // Stop all jobs + jobs.maybeFinishJobs(1); + + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP); + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ); + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG); + + startPendingJobs(jobs); + + // Random order, but we should have 4 TOP, 1 EJ, and 1 BG running. + assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1); + assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1); + assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); + // At this point, all TOP should be running (or have already run). + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(5); + + // Stop only a bg job and make sure the counter only allows another bg job to start. + jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1); + mWorkCountTracker.onJobFinished(WORK_TYPE_BG); + + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE); + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_NONE); + assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG); + + startPendingJobs(jobs); + + assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1); + assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1); + assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java index 32445fd1a47d..2eedc3251daa 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java @@ -19,19 +19,17 @@ package com.android.server.locksettings; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; - import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.security.GeneralSecurityException; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; -import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; /** * atest FrameworksServicesTests:RebootEscrowDataTest @@ -41,22 +39,18 @@ public class RebootEscrowDataTest { private RebootEscrowKey mKey; private SecretKey mKeyStoreEncryptionKey; - private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException { - KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); - generator.init(new KeyGenParameterSpec.Builder( - "reboot_escrow_data_test_key", - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setKeySize(256) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .build()); - return generator.generateKey(); - } + // Hex encoding of a randomly generated AES key for test. + private static final byte[] TEST_AES_KEY = new byte[] { + 0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61, + 0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31, + 0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09, + 0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23, + }; @Before public void generateKey() throws Exception { mKey = RebootEscrowKey.generate(); - mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey(); + mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES"); } private static byte[] getTestSp() { @@ -114,4 +108,23 @@ public class RebootEscrowDataTest { assertThat(decrypted, is(testSp)); } + @Test + public void fromEncryptedData_legacyVersion_success() throws Exception { + byte[] testSp = getTestSp(); + byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp); + + // Write a legacy blob encrypted only by k_s. + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(1); + dos.writeByte(3); + dos.write(ksEncryptedBlob); + byte[] legacyBlob = bos.toByteArray(); + + RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null); + + assertThat(actual.getSpVersion(), is((byte) 3)); + assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes())); + assertThat(actual.getSyntheticPassword(), is(testSp)); + } } 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 a4ba4c86a8fd..a896f1b0d60f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -43,6 +43,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; +import android.os.Handler; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserManager; @@ -155,6 +156,11 @@ public class RebootEscrowManagerTests { } @Override + void post(Handler handler, Runnable runnable) { + runnable.run(); + } + + @Override public UserManager getUserManager() { return mUserManager; } @@ -369,7 +375,7 @@ public class RebootEscrowManagerTests { @Test public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception { - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); } @Test @@ -401,7 +407,7 @@ public class RebootEscrowManagerTests { doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); @@ -435,7 +441,7 @@ public class RebootEscrowManagerTests { when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection).unwrap(any(), anyLong()); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); @@ -466,7 +472,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); verify(mInjected, never()).reportMetric(anyBoolean()); } @@ -493,7 +499,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected, never()).reportMetric(anyBoolean()); } @@ -527,7 +533,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected).reportMetric(eq(true)); } @@ -557,7 +563,7 @@ public class RebootEscrowManagerTests { ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertFalse(metricsSuccessCaptor.getValue()); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java index bc1e025dd99f..28b737b412d2 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -42,7 +43,6 @@ import org.junit.runner.RunWith; import org.mockito.stubbing.Answer; import java.io.File; -import java.io.IOException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -130,7 +130,7 @@ public class RebootEscrowProviderServerBasedImplTests { @Test public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception { when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); - doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong()); + doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong()); assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); diff --git a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java deleted file mode 100644 index 79935c23774f..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java +++ /dev/null @@ -1,135 +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.pm; - -import android.content.Context; -import android.content.pm.PackageInstaller; -import android.os.storage.StorageManager; -import android.platform.test.annotations.Presubmit; - -import com.android.internal.os.BackgroundThread; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.io.File; -import java.util.function.Predicate; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -@Presubmit -@RunWith(JUnit4.class) -public class StagingManagerTest { - @Rule - public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - private File mTmpDir; - private StagingManager mStagingManager; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - StorageManager storageManager = Mockito.mock(StorageManager.class); - Context context = Mockito.mock(Context.class); - when(storageManager.isCheckpointSupported()).thenReturn(true); - when(context.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null); - when(context.getSystemService(eq(Context.STORAGE_SERVICE))).thenReturn(storageManager); - - mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest"); - mStagingManager = new StagingManager(context, null); - } - - /** - * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping - * check. - */ - @Test - public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes() - throws Exception { - // Create 2 sessions with overlapping packages - StagingManager.StagedSession session1 = createSession(111, "com.foo", 1); - StagingManager.StagedSession session2 = createSession(222, "com.foo", 2); - - mStagingManager.createSession(session1); - mStagingManager.createSession(session2); - // Session1 should not fail in spite of the overlapping packages - mStagingManager.checkNonOverlappingWithStagedSessions(session1); - // Session2 should fail due to overlapping packages - assertThrows(PackageManagerException.class, - () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2)); - } - - private StagingManager.StagedSession createSession(int sessionId, String packageName, - long committedMillis) { - PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( - PackageInstaller.SessionParams.MODE_FULL_INSTALL); - params.isStaged = true; - - InstallSource installSource = InstallSource.create("testInstallInitiator", - "testInstallOriginator", "testInstaller", "testAttributionTag"); - - PackageInstallerSession session = new PackageInstallerSession( - /* callback */ null, - /* context */ null, - /* pm */ null, - /* sessionProvider */ null, - /* looper */ BackgroundThread.getHandler().getLooper(), - /* stagingManager */ null, - /* sessionId */ sessionId, - /* userId */ 456, - /* installerUid */ -1, - /* installSource */ installSource, - /* sessionParams */ params, - /* createdMillis */ 0L, - /* committedMillis */ committedMillis, - /* stageDir */ mTmpDir, - /* stageCid */ null, - /* files */ null, - /* checksums */ null, - /* prepared */ true, - /* committed */ true, - /* destroyed */ false, - /* sealed */ false, // Setting to true would trigger some PM logic. - /* childSessionIds */ null, - /* parentSessionId */ -1, - /* isReady */ false, - /* isFailed */ false, - /* isApplied */false, - /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR, - /* stagedSessionErrorMessage */ "no error"); - - StagingManager.StagedSession stagedSession = spy(session.mStagedSession); - doReturn(packageName).when(stagedSession).getPackageName(); - doAnswer(invocation -> { - Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0); - return filter.test(stagedSession); - }).when(stagedSession).sessionContains(any()); - return stagedSession; - } -} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java index 72c40ea5a2be..014bfd2d40e4 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java @@ -23,7 +23,7 @@ import android.os.Vibrator; import androidx.annotation.NonNull; /** Fake implementation of {@link Vibrator} for service tests. */ -public final class FakeVibrator extends Vibrator { +final class FakeVibrator extends Vibrator { private int mDefaultHapticFeedbackIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM; private int mDefaultNotificationIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM; 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 f562c1613413..4634e12f1530 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -33,7 +33,7 @@ import java.util.Map; * Provides {@link VibratorController} with controlled vibrator hardware capabilities and * interactions. */ -public final class FakeVibratorControllerProvider { +final class FakeVibratorControllerProvider { private static final int EFFECT_DURATION = 20; diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java index e71c2f5ba8da..8c62b7fe235e 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java @@ -67,9 +67,8 @@ public class InputDeviceDelegateTest { private static final String REASON = "some reason"; private static final VibrationAttributes VIBRATION_ATTRIBUTES = new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build(); - private static final VibrationEffect EFFECT = VibrationEffect.createOneShot(100, 255); private static final CombinedVibrationEffect SYNCED_EFFECT = - CombinedVibrationEffect.createSynced(EFFECT); + CombinedVibrationEffect.createSynced(VibrationEffect.createOneShot(100, 255)); @Rule public MockitoRule rule = MockitoJUnit.rule(); @@ -105,6 +104,7 @@ public class InputDeviceDelegateTest { mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false); assertFalse(mInputDeviceDelegate.isAvailable()); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); mInputDeviceDelegate.onInputDeviceAdded(1); @@ -118,6 +118,7 @@ public class InputDeviceDelegateTest { mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); assertFalse(mInputDeviceDelegate.isAvailable()); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]); when(mIInputManagerMock.getInputDevice(eq(1))) .thenReturn(createInputDeviceWithoutVibrator(1)); updateInputDevices(new int[]{1}); @@ -132,6 +133,7 @@ public class InputDeviceDelegateTest { mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); assertFalse(mInputDeviceDelegate.isAvailable()); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); updateInputDevices(new int[]{1}); @@ -142,6 +144,7 @@ public class InputDeviceDelegateTest { @Test public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false); @@ -153,6 +156,7 @@ public class InputDeviceDelegateTest { @Test public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]); when(mIInputManagerMock.getInputDevice(eq(1))) .thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1)); @@ -167,6 +171,7 @@ public class InputDeviceDelegateTest { @Test public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]); when(mIInputManagerMock.getInputDevice(eq(1))) .thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null); @@ -181,6 +186,7 @@ public class InputDeviceDelegateTest { @Test public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0], new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))) .thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1)); @@ -195,8 +201,10 @@ public class InputDeviceDelegateTest { @Test public void onInputDeviceRemoved_removesDevice() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn( createInputDeviceWithoutVibrator(1)); + when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2)); mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); @@ -209,7 +217,9 @@ public class InputDeviceDelegateTest { @Test public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); + when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2)); mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); @@ -223,6 +233,7 @@ public class InputDeviceDelegateTest { public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]); when(mIInputManagerMock.getInputDevice(eq(1))) .thenReturn(createInputDeviceWithoutVibrator(1)); mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); @@ -240,14 +251,16 @@ public class InputDeviceDelegateTest { public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); + when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2)); mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); assertTrue(mInputDeviceDelegate.vibrateIfAvailable( UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES)); - verify(mIInputManagerMock).vibrate(eq(1), same(EFFECT), any()); - verify(mIInputManagerMock).vibrate(eq(2), same(EFFECT), any()); + verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any()); + verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any()); } @Test @@ -261,7 +274,9 @@ public class InputDeviceDelegateTest { public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2}); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); + when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2)); mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); 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 3ff8e76a3707..1b7e1ca6a014 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,7 @@ import android.os.SystemClock; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.test.TestLooper; +import android.platform.test.annotations.LargeTest; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; @@ -709,6 +710,7 @@ public class VibrationThreadTest { assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes()); } + @LargeTest @Test public void vibrate_withWaveform_totalVibrationTimeRespected() { int totalDuration = 10_000; // 10s diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index da3d1d6187fc..ba0a472c80dd 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.vibrator; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -73,9 +73,7 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; -import com.android.server.vibrator.FakeVibrator; -import com.android.server.vibrator.FakeVibratorControllerProvider; -import com.android.server.vibrator.VibratorController; +import com.android.server.LocalServices; import org.junit.After; import org.junit.Before; @@ -533,28 +531,15 @@ public class VibratorManagerServiceTest { mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); + when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); + when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); VibratorManagerService service = createService(); - // Prebaked vibration will play fallback waveform on input device. - ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any()); - assertTrue(captor.getValue() instanceof VibrationEffect.Waveform); - - VibrationEffect[] effects = new VibrationEffect[]{ - VibrationEffect.createOneShot(100, 128), - VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1), - VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) - .compose(), - }; - - for (VibrationEffect effect : effects) { - vibrate(service, effect, ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any()); - } + CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( + VibrationEffect.createOneShot(10, 10)); + vibrate(service, effect, ALARM_ATTRS); + 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(), diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS new file mode 100644 index 000000000000..aa87958f1d53 --- /dev/null +++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/net/OWNERS diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index ec28baf53d3c..07475e955785 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1686,6 +1686,11 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Override + protected void ensureFilters(ServiceInfo si, int userId) { + + } + + @Override protected void loadDefaultsFromConfig() { mDefaultComponents.addAll(mDefaults); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index be489c3bcd12..5614aa2a165d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -394,7 +394,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { "disabledMessage", 0, "disabledMessageResName", null, null, 0, null, 0, 0, 0, "iconResName", "bitmapPath", null, 0, - null, null); + null, null, 0); return si; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index afcf08ef1562..80a046a1e8bb 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -15,6 +15,11 @@ */ package com.android.server.notification; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; +import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; + +import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -27,11 +32,14 @@ import android.app.INotificationManager; import android.content.ComponentName; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.VersionedPackage; +import android.os.Bundle; import android.service.notification.NotificationListenerFilter; +import android.service.notification.NotificationListenerService; import android.util.ArraySet; import android.util.Pair; +import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -47,8 +55,6 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.List; public class NotificationListenersTest extends UiServiceTestCase { @@ -80,22 +86,21 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test public void testReadExtraTag() throws Exception { - String xml = "<req_listeners>" + String xml = "<" + TAG_REQUESTED_LISTENERS+ ">" + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">" + "<allowed types=\"7\" />" - + "<disallowed pkgs=\"\" />" + "</listener>" + "<listener component=\"" + mCn2.flattenToString() + "\" user=\"10\">" + "<allowed types=\"4\" />" - + "<disallowed pkgs=\"something\" />" + + "<disallowed pkg=\"pkg1\" uid=\"243\"/>" + "</listener>" - + "</req_listeners>"; + + "</" + TAG_REQUESTED_LISTENERS + ">"; TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setInput(new BufferedInputStream( new ByteArrayInputStream(xml.getBytes())), null); parser.nextTag(); - mListeners.readExtraTag("req_listeners", parser); + mListeners.readExtraTag(TAG_REQUESTED_LISTENERS, parser); validateListenersFromXml(); } @@ -103,8 +108,9 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test public void testWriteExtraTag() throws Exception { NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); NotificationListenerFilter nlf2 = - new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"})); + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2); @@ -134,16 +140,18 @@ public class NotificationListenersTest extends UiServiceTestCase { assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes()) .isEqualTo(4); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)) .getDisallowedPackages()) - .contains("something"); + .contains(a1); } @Test public void testOnUserRemoved() { NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); NotificationListenerFilter nlf2 = - new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"})); + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2); @@ -155,58 +163,68 @@ public class NotificationListenersTest extends UiServiceTestCase { } @Test - public void testOnUserUnlocked() { + public void testEnsureFilters_newServiceNoMetadata() { + ServiceInfo si = new ServiceInfo(); + si.packageName = "new2"; + si.name = "comp2"; + + mListeners.ensureFilters(si, 0); + + assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isNull(); + } + + @Test + public void testEnsureFilters_preExisting() { // one exists already, say from xml + VersionedPackage a1 = new VersionedPackage("pkg1", 243); NotificationListenerFilter nlf = - new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"})); + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf); + ServiceInfo siOld = new ServiceInfo(); + siOld.packageName = mCn2.getPackageName(); + siOld.name = mCn2.getClassName(); + + mListeners.ensureFilters(siOld, 0); - // new service exists or backfilling on upgrade to S + assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isEqualTo(nlf); + } + + @Test + public void testEnsureFilters_newServiceWithMetadata() { ServiceInfo si = new ServiceInfo(); - si.permission = mListeners.getConfig().bindPermission; si.packageName = "new"; si.name = "comp"; - ResolveInfo ri = new ResolveInfo(); - ri.serviceInfo = si; + si.metaData = new Bundle(); + si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1,2"); - // incorrect service - ServiceInfo si2 = new ServiceInfo(); - ResolveInfo ri2 = new ResolveInfo(); - ri2.serviceInfo = si2; - si2.packageName = "new2"; - si2.name = "comp2"; + mListeners.ensureFilters(si, 0); - List<ResolveInfo> ris = new ArrayList<>(); - ris.add(ri); - ris.add(ri2); - - when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(ris); + assertThat(mListeners.getNotificationListenerFilter( + Pair.create(si.getComponentName(), 0)).getTypes()) + .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING); + } - mListeners.onUserUnlocked(0); + @Test + public void testEnsureFilters_newServiceWithEmptyMetadata() { + ServiceInfo si = new ServiceInfo(); + si.packageName = "new"; + si.name = "comp"; + si.metaData = new Bundle(); + si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, ""); - assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes()) - .isEqualTo(4); - assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) - .getDisallowedPackages()) - .contains("something"); + mListeners.ensureFilters(si, 0); assertThat(mListeners.getNotificationListenerFilter( Pair.create(si.getComponentName(), 0)).getTypes()) - .isEqualTo(15); - assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0)) - .getDisallowedPackages()) - .isEmpty(); - - assertThat(mListeners.getNotificationListenerFilter(Pair.create(si2.getComponentName(), 0))) - .isNull(); - + .isEqualTo(0); } @Test public void testOnPackageChanged() { NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); NotificationListenerFilter nlf2 = - new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"})); + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2); @@ -224,8 +242,9 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test public void testOnPackageChanged_removing() { NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); NotificationListenerFilter nlf2 = - new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"})); + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2); @@ -239,4 +258,21 @@ public class NotificationListenersTest extends UiServiceTestCase { .isEqualTo(4); } + @Test + public void testOnPackageChanged_removingDisallowedPackage() { + NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); + NotificationListenerFilter nlf2 = + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); + mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); + mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2); + + String[] pkgs = new String[] {"pkg1"}; + int[] uids = new int[] {243}; + mListeners.onPackagesChanged(true, pkgs, uids); + + assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)) + .getDisallowedPackages()).isEmpty(); + } + } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index e8888f496a01..a64050996d42 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -464,7 +464,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Setup managed services when(mNlf.isTypeAllowed(anyInt())).thenReturn(true); - when(mNlf.isPackageAllowed(anyString())).thenReturn(true); + when(mNlf.isPackageAllowed(any())).thenReturn(true); when(mNlf.isPackageAllowed(null)).thenReturn(true); when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf); mListener = mListeners.new ManagedServiceInfo( @@ -7307,7 +7307,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testIsVisibleToListener_disallowedPackage() { - when(mNlf.isPackageAllowed(null)).thenReturn(false); + when(mNlf.isPackageAllowed(any())).thenReturn(false); StatusBarNotification sbn = mock(StatusBarNotification.class); when(sbn.getUserId()).thenReturn(10); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 72b84396e985..aa1110cd55a7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -719,7 +719,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); assertTrue(activity.hasSavedState()); - ActivityRecord.activityResumedLocked(activity.appToken); + ActivityRecord.activityResumedLocked(activity.appToken, false /* handleSplashScreenExit */); assertFalse(activity.hasSavedState()); assertNull(activity.getSavedState()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java index 5597be93c1f1..2686a2429492 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java @@ -27,6 +27,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -118,6 +119,19 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase { } @Test + public void testRegisterOrganizer_ignoreUntrustedDisplay() throws RemoteException { + doReturn(false).when(mDisplayContent).isTrusted(); + + final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder()); + List<DisplayAreaAppearedInfo> infos = mOrganizerController + .registerOrganizer(organizer, FEATURE_VENDOR_FIRST).getList(); + + assertThat(infos).isEmpty(); + verify(organizer, never()).onDisplayAreaAppeared(any(DisplayAreaInfo.class), + any(SurfaceControl.class)); + } + + @Test public void testCreateTaskDisplayArea_topBelowRoot() { final String newTdaName = "testTda"; final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder()); @@ -186,13 +200,20 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase { @Test public void testCreateTaskDisplayArea_invalidDisplayAndRoot() { final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder()); + assertThrows(IllegalArgumentException.class, () -> mOrganizerController.createTaskDisplayArea( organizer, SystemServicesTestRule.sNextDisplayId + 1, FEATURE_ROOT, "testTda")); + assertThrows(IllegalArgumentException.class, () -> mOrganizerController.createTaskDisplayArea( organizer, DEFAULT_DISPLAY, FEATURE_ROOT - 1, "testTda")); + + doReturn(false).when(mDisplayContent).isTrusted(); + assertThrows(IllegalArgumentException.class, () -> + mOrganizerController.createTaskDisplayArea( + organizer, DEFAULT_DISPLAY, FEATURE_ROOT, "testTda")); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index 89b962b96baf..d4c956db90a9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -43,11 +43,15 @@ import static com.android.server.wm.testing.Assert.assertThrows; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -57,6 +61,7 @@ import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; +import android.window.IDisplayAreaOrganizer; import com.google.android.collect.Lists; @@ -525,6 +530,42 @@ public class DisplayAreaTest extends WindowTestsBase { assertThat(mDisplayContent.getOrientationRequestingTaskDisplayArea()).isEqualTo(tda); } + @Test + public void testDisplayContentUpdateDisplayAreaOrganizers_onDisplayAreaAppeared() { + final DisplayArea<WindowContainer> displayArea = new DisplayArea<>( + mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST); + final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class); + spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController); + when(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController + .getOrganizerByFeature(FEATURE_VENDOR_FIRST)) + .thenReturn(mockDisplayAreaOrganizer); + + mDisplayContent.addChild(displayArea, 0); + mDisplayContent.updateDisplayAreaOrganizers(); + + assertEquals(mockDisplayAreaOrganizer, displayArea.mOrganizer); + verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController) + .onDisplayAreaAppeared( + eq(mockDisplayAreaOrganizer), + argThat(it -> it == displayArea && it.getSurfaceControl() != null)); + } + + @Test + public void testRemoveImmediately_onDisplayAreaVanished() { + final DisplayArea<WindowContainer> displayArea = new DisplayArea<>( + mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST); + final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class); + displayArea.mOrganizer = mockDisplayAreaOrganizer; + spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController); + mDisplayContent.addChild(displayArea, 0); + + displayArea.removeImmediately(); + + assertNull(displayArea.mOrganizer); + verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController) + .onDisplayAreaVanished(mockDisplayAreaOrganizer, displayArea); + } + private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> { private TestDisplayArea(WindowManagerService wms, Rect bounds) { super(wms, ANY, "half display area"); 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 b4fd3024a634..781cfec77f08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1834,7 +1834,7 @@ public class DisplayContentTests extends WindowTestsBase { mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */); } - private void performLayout(DisplayContent dc) { + static void performLayout(DisplayContent dc) { dc.setLayoutNeeded(); dc.performLayout(true /* initial */, false /* updateImeWindows */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index cc4d4eaa9e8b..e843dd71381f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -482,8 +482,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testHandleActivitySizeCompatModeChanged() { setUpDisplaySizeWithApp(1000, 2000); doReturn(true).when(mTask).isOrganized(); - ActivityRecord activity = mActivity; - activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); + mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertFitted(); @@ -499,12 +498,12 @@ public class SizeCompatTests extends WindowTestsBase { // Make the activity resizable again by restarting it clearInvocations(mTask); - activity.info.resizeMode = RESIZE_MODE_RESIZEABLE; - activity.mVisibleRequested = true; - activity.restartProcessIfVisible(); + mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE; + mActivity.mVisibleRequested = true; + mActivity.restartProcessIfVisible(); // The full lifecycle isn't hooked up so manually set state to resumed - activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); - mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity); + mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); + mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity); // Expect null token when switching to non-size-compat mode activity. verify(mTask).onSizeCompatActivityChanged(); @@ -515,6 +514,46 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testHandleActivitySizeCompatModeChangedOnDifferentTask() { + setUpDisplaySizeWithApp(1000, 2000); + doReturn(true).when(mTask).isOrganized(); + mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged"); + prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + assertFitted(); + + // Resize the display so that the activity exercises size-compat mode. + resizeDisplay(mTask.mDisplayContent, 1000, 2500); + + // Expect the exact token when the activity is in size compatibility mode. + verify(mTask).onSizeCompatActivityChanged(); + ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo(); + + assertEquals(mActivity.appToken, taskInfo.topActivityToken); + assertTrue(taskInfo.topActivityInSizeCompat); + + // Create another Task to hold another size compat activity. + clearInvocations(mTask); + final Task secondTask = new TaskBuilder(mSupervisor).setDisplay(mTask.getDisplayContent()) + .setCreateActivity(true).build(); + final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity(); + doReturn(true).when(secondTask).isOrganized(); + secondActivity.setState(Task.ActivityState.RESUMED, + "testHandleActivitySizeCompatModeChanged"); + prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + + // Resize the display so that the activity exercises size-compat mode. + resizeDisplay(mTask.mDisplayContent, 1000, 3000); + + // Expect the exact token when the activity is in size compatibility mode. + verify(secondTask).onSizeCompatActivityChanged(); + verify(mTask, never()).onSizeCompatActivityChanged(); + taskInfo = secondTask.getTaskInfo(); + + assertEquals(secondActivity.appToken, taskInfo.topActivityToken); + assertTrue(taskInfo.topActivityInSizeCompat); + } + + @Test public void testShouldUseSizeCompatModeOnResizableTask() { setUpDisplaySizeWithApp(1000, 2500); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 77fca3d2fdeb..9c1614393554 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -549,6 +549,9 @@ public class WindowOrganizerTests extends WindowTestsBase { public void removeStartingWindow(int taskId) { } @Override + public void copySplashScreenView(int taskId) { } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @Override @@ -609,10 +612,10 @@ public class WindowOrganizerTests extends WindowTestsBase { public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } - @Override public void removeStartingWindow(int taskId) { } - + @Override + public void copySplashScreenView(int taskId) { } @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @@ -685,7 +688,8 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void removeStartingWindow(int taskId) { } - + @Override + public void copySplashScreenView(int taskId) { } @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @@ -829,6 +833,8 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void removeStartingWindow(int taskId) { } @Override + public void copySplashScreenView(int taskId) { } + @Override public void onTaskAppeared(RunningTaskInfo info, SurfaceControl leash) { mInfo = info; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 3231f8b6551a..896969548af3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -67,11 +67,14 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.when; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.view.Gravity; import android.view.InputWindowHandle; import android.view.InsetsState; import android.view.SurfaceControl; @@ -559,6 +562,46 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(window.isVisibleByPolicy()); } + @Test + public void testCompatOverrideScale() { + final float overrideScale = 2; // 0.5x on client side. + final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages; + spyOn(cmp); + doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt()); + final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win"); + makeWindowVisible(w); + w.setRequestedSize(100, 200); + w.mAttrs.width = w.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT; + w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT; + DisplayContentTests.performLayout(mDisplayContent); + + // Frame on screen = 100x200. Compat frame on client = 50x100. + final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame); + unscaledCompatFrame.scale(overrideScale); + assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame); + + // Surface should apply the scale. + w.prepareSurfaces(); + verify(w.getPendingTransaction()).setMatrix(w.getSurfaceControl(), + overrideScale, 0, 0, overrideScale); + + // According to "dp * density / 160 = px", density is scaled and the size in dp is the same. + final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked( + mContext.getApplicationInfo()); + final Configuration winConfig = w.getConfiguration(); + final Configuration clientConfig = new Configuration(w.getConfiguration()); + compatInfo.applyToConfiguration(clientConfig.densityDpi, clientConfig); + + assertEquals(winConfig.screenWidthDp, clientConfig.screenWidthDp); + assertEquals(winConfig.screenHeightDp, clientConfig.screenHeightDp); + assertEquals(winConfig.smallestScreenWidthDp, clientConfig.smallestScreenWidthDp); + assertEquals(winConfig.densityDpi, (int) (clientConfig.densityDpi * overrideScale)); + + final Rect unscaledClientBounds = new Rect(clientConfig.windowConfiguration.getBounds()); + unscaledClientBounds.scale(overrideScale); + assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds); + } + @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE }) @Test public void testRequestDrawIfNeeded() { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index eb6c6ed349de..c13d6b19bf1d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -337,6 +337,7 @@ class WindowTestsBase extends SystemServiceTestsBase { final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); attrs.setTitle(name); + attrs.packageName = "test"; final WindowState w = new WindowState(service, session, iWindow, token, parent, OP_NONE, attrs, VISIBLE, ownerId, userId, @@ -1143,6 +1144,9 @@ class WindowTestsBase extends SystemServiceTestsBase { public void removeStartingWindow(int taskId) { } @Override + public void copySplashScreenView(int taskId) { + } + @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) { } @Override diff --git a/services/usb/OWNERS b/services/usb/OWNERS index 8ee72b577f3c..60172a36128e 100644 --- a/services/usb/OWNERS +++ b/services/usb/OWNERS @@ -1,6 +1,5 @@ badhri@google.com elaurent@google.com -moltmann@google.com albertccwang@google.com jameswei@google.com howardyen@google.com
\ No newline at end of file diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 21cf3e57115d..3f6162d6422b 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5351,11 +5351,28 @@ public class CarrierConfigManager { public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED = KEY_PREFIX + "suggestion_ssid_list_with_mac_randomization_disabled"; + /** + * Avoid SoftAp in 5GHz if cellular is on unlicensed 5Ghz using License Assisted Access + * (LAA). + */ + public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL = + KEY_PREFIX + "avoid_5ghz_softap_for_laa_bool"; + + /** + * Avoid Wifi Direct in 5GHz if cellular is on unlicensed 5Ghz using License Assisted + * Access (LAA). + */ + public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL = + KEY_PREFIX + "avoid_5ghz_wifi_direct_for_laa_bool"; + + private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); defaults.putInt(KEY_HOTSPOT_MAX_CLIENT_COUNT, 0); defaults.putStringArray(KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED, new String[0]); + defaults.putBoolean(KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL, false); + defaults.putBoolean(KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL, false); return defaults; } diff --git a/telephony/java/android/telephony/RadioInterfaceCapabilities.java b/telephony/java/android/telephony/RadioInterfaceCapabilities.java deleted file mode 100644 index 7c7eb9fbbeb2..000000000000 --- a/telephony/java/android/telephony/RadioInterfaceCapabilities.java +++ /dev/null @@ -1,53 +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 android.telephony; - -import android.util.ArraySet; - -/** - * Contains the set of supported capabilities that the Radio Interface supports on this device. - * - * @hide - */ -public class RadioInterfaceCapabilities { - - private final ArraySet<String> mSupportedCapabilities; - - - public RadioInterfaceCapabilities() { - mSupportedCapabilities = new ArraySet<>(); - } - - /** - * Marks a capability as supported - * - * @param capabilityName the name of the capability - */ - public void addSupportedCapability( - @TelephonyManager.RadioInterfaceCapability String capabilityName) { - mSupportedCapabilities.add(capabilityName); - } - - /** - * Whether the capability is supported - * - * @param capabilityName the name of the capability - */ - public boolean isSupported(String capabilityName) { - return mSupportedCapabilities.contains(capabilityName); - } -} diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 16ffd9e4a44e..ee3a0ef5f4fe 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -8491,6 +8491,11 @@ public class TelephonyManager { * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * <p> + * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} + * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then + * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, + * setPreferredNetworkTypesBitmap is used instead. * * @param subId the id of the subscription to set the preferred network type for. * @param networkType the preferred network type @@ -8524,6 +8529,11 @@ public class TelephonyManager { * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * <p> + * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} + * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then + * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, + * setPreferredNetworkTypesBitmap is used instead. * * @param networkTypeBitmask The bitmask of preferred network types. * @return true on success; false on any failure. @@ -8550,6 +8560,11 @@ public class TelephonyManager { * Set the allowed network types of the device. This is for carrier or privileged apps to * enable/disable certain network types on the device. The user preferred network types should * be set through {@link #setPreferredNetworkTypeBitmask}. + * <p> + * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} + * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then + * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, + * setPreferredNetworkTypesBitmap is used instead. * * @param allowedNetworkTypes The bitmask of allowed network types. * @return true on success; false on any failure. @@ -8624,6 +8639,11 @@ public class TelephonyManager { * </ol> * This API will result in allowing an intersection of allowed network types for all reasons, * including the configuration done through other reasons. + * <p> + * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} + * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then + * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, + * setPreferredNetworkTypesBitmap is used instead. * * @param reason the reason the allowed network type change is taking place * @param allowedNetworkTypes The bitmask of allowed network types. @@ -14861,10 +14881,24 @@ public class TelephonyManager { public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE"; + /** + * Indicates whether {@link #setPreferredNetworkType}, {@link + * #setPreferredNetworkTypeBitmask}, {@link #setAllowedNetworkTypes} and + * {@link #setAllowedNetworkTypesForReason} rely on + * setAllowedNetworkTypesBitmap instead of setPreferredNetworkTypesBitmap on the radio + * interface. + * + * @hide + */ + @SystemApi + public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = + "CAPABILITY_ALLOWED_NETWORK_TYPES_USED"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @StringDef(prefix = "CAPABILITY_", value = { CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE, + CAPABILITY_ALLOWED_NETWORK_TYPES_USED, }) public @interface RadioInterfaceCapability {} diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt index eb04f6907748..ac9e6817a230 100644 --- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt @@ -76,11 +76,11 @@ class PlatformCompatCommandNotInstalledTest { Params(enableDisable = null, targetSdk = 29, result = false), Params(enableDisable = null, targetSdk = 30, result = true), - Params(enableDisable = true, targetSdk = 29, result = true), + Params(enableDisable = true, targetSdk = 29, result = false), Params(enableDisable = true, targetSdk = 30, result = true), Params(enableDisable = false, targetSdk = 29, result = false), - Params(enableDisable = false, targetSdk = 30, result = false) + Params(enableDisable = false, targetSdk = 30, result = true) ) } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 401d87af5a33..0508125edfc8 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -125,21 +125,11 @@ public class StagedRollbackTest { } /** - * Test rollbacks of staged installs involving only apks with bad update. - * Trigger rollback phase. - */ - @Test - public void testBadApkOnly_Phase3_Crash() throws Exception { - // One more crash to trigger rollback - RollbackUtils.sendCrashBroadcast(TestApp.A, 1); - } - - /** * Test rollbacks of staged installs involving only apks. * Confirm rollback phase. */ @Test - public void testBadApkOnly_Phase4_VerifyRollback() throws Exception { + public void testBadApkOnly_Phase3_VerifyRollback() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); InstallUtils.processUserData(TestApp.A); @@ -447,8 +437,10 @@ public class StagedRollbackTest { Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), Rollback.from(TestApp.A, 0).to(TestApp.A1)); - // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback - RollbackUtils.sendCrashBroadcast(TestApp.A, 5); + // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT-1 times + RollbackUtils.sendCrashBroadcast(TestApp.A, 4); + // Sleep for a while to make sure we don't trigger rollback + Thread.sleep(TimeUnit.SECONDS.toMillis(30)); } @Test @@ -504,6 +496,45 @@ public class StagedRollbackTest { } @Test + public void testWatchdogMonitorsAcrossReboots_Phase1_Install() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + + Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit(); + } + + @Test + public void testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + + // Trigger rollback of test app. + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, + Integer.toString(5), false); + + // The final crash that causes rollback will come from the host side. + RollbackUtils.sendCrashBroadcast(TestApp.A, 4); + } + + @Test + public void testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback() { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + + RollbackManager rm = RollbackUtils.getRollbackManager(); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(rollback).isNotNull(); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); + assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2); + assertThat(rollback).isStaged(); + assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1); + } + + @Test public void hasMainlineModule() throws Exception { String pkgName = getModuleMetadataPackageName(); boolean existed = InstrumentationRegistry.getInstrumentation().getContext() diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 1d5730fb4427..65fb7b6c8cc6 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -153,13 +153,14 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().reboot(); runPhase("testBadApkOnly_Phase2_VerifyInstall"); - // Trigger rollback and wait for reboot to happen - runPhase("testBadApkOnly_Phase3_Crash"); + // Launch the app to crash to trigger rollback + startActivity(TESTAPP_A); + // Wait for reboot to happen waitForDeviceNotAvailable(2, TimeUnit.MINUTES); getDevice().waitForDeviceAvailable(); - runPhase("testBadApkOnly_Phase4_VerifyRollback"); + runPhase("testBadApkOnly_Phase3_VerifyRollback"); assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A); assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); @@ -304,8 +305,10 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().reboot(); // Verify apex was installed and then crash the apk runPhase("testRollbackApexWithApkCrashing_Phase2_Crash"); - // Wait for crash to trigger rollback - waitForDeviceNotAvailable(5, TimeUnit.MINUTES); + // Launch the app to crash to trigger rollback + startActivity(TESTAPP_A); + // Wait for reboot to happen + waitForDeviceNotAvailable(2, TimeUnit.MINUTES); getDevice().waitForDeviceAvailable(); // Verify rollback occurred due to crash of apk-in-apex runPhase("testRollbackApexWithApkCrashing_Phase3_VerifyRollback"); @@ -551,6 +554,30 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { }); } + /** + * Tests that packages are monitored across multiple reboots. + */ + @Test + public void testWatchdogMonitorsAcrossReboots() throws Exception { + runPhase("testWatchdogMonitorsAcrossReboots_Phase1_Install"); + + // The first reboot will make the rollback available. + // Information about which packages are monitored will be persisted to a file before the + // second reboot, and read from disk after the second reboot. + getDevice().reboot(); + getDevice().reboot(); + + runPhase("testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall"); + + // Launch the app to crash to trigger rollback + startActivity(TESTAPP_A); + // Wait for reboot to happen + waitForDeviceNotAvailable(2, TimeUnit.MINUTES); + getDevice().waitForDeviceAvailable(); + + runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback"); + } + private void pushTestApex() throws Exception { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; @@ -631,6 +658,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } } + private void startActivity(String packageName) throws Exception { + String cmd = "am start -S -a android.intent.action.MAIN " + + "-c android.intent.category.LAUNCHER " + packageName; + getDevice().executeShellCommand(cmd); + } + private void crashProcess(String processName, int numberOfCrashes) throws Exception { String pid = ""; String lastPid = "invalid"; diff --git a/tests/StagedInstallTest/StagedInstallInternalTest.xml b/tests/StagedInstallTest/StagedInstallInternalTest.xml index 1b8fa672fe38..1f22cae8f3cf 100644 --- a/tests/StagedInstallTest/StagedInstallInternalTest.xml +++ b/tests/StagedInstallTest/StagedInstallInternalTest.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> <configuration description="Runs the internal staged install tests"> - <option name="test-suite-tag" value="StagedInstallTest" /> + <option name="test-suite-tag" value="StagedInstallInternalTest" /> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="StagedInstallInternalTestApp.apk" /> diff --git a/tests/StagedInstallTest/TEST_MAPPING b/tests/StagedInstallTest/TEST_MAPPING index fa2a60b21b50..cc31f2c98425 100644 --- a/tests/StagedInstallTest/TEST_MAPPING +++ b/tests/StagedInstallTest/TEST_MAPPING @@ -1,6 +1,16 @@ { "presubmit-large": [ { + "name": "StagedInstallInternalTest", + "options": [ + { + "exclude-annotation": "android.platform.test.annotations.LargeTest" + } + ] + } + ], + "postsubmit": [ + { "name": "StagedInstallInternalTest" } ] diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index 2201efd3a7ac..8dc53ac26715 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import android.cts.install.lib.host.InstallUtilsHost; +import android.platform.test.annotations.LargeTest; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.ddmlib.Log; @@ -201,6 +202,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { // Test rollback-app command waits for staged sessions to be ready @Test + @LargeTest public void testAdbRollbackAppWaitsForStagedReady() throws Exception { assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml index 6bed5a80d129..4c60ccf60615 100644 --- a/tests/net/AndroidManifest.xml +++ b/tests/net/AndroidManifest.xml @@ -48,6 +48,7 @@ <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> <uses-permission android:name="android.permission.NETWORK_FACTORY" /> <uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" /> + <uses-permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java index d232a507454d..fd29a9539de8 100644 --- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java +++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java @@ -40,7 +40,7 @@ import java.util.Map; @SmallTest public class OemNetworkPreferencesTest { - private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT; + private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; private static final String TEST_PACKAGE = "com.google.apps.contacts"; private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder(); @@ -54,7 +54,7 @@ public class OemNetworkPreferencesTest { @Test public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() { assertThrows(NullPointerException.class, - () -> mBuilder.removeNetworkPreference(null)); + () -> mBuilder.clearNetworkPreference(null)); } @Test @@ -129,7 +129,7 @@ public class OemNetworkPreferencesTest { assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); - mBuilder.removeNetworkPreference(TEST_PACKAGE); + mBuilder.clearNetworkPreference(TEST_PACKAGE); networkPreferences = mBuilder.build().getNetworkPreferences(); assertFalse(networkPreferences.containsKey(TEST_PACKAGE)); diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index e1da3d0ae2b3..dc9e587332cb 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -17,7 +17,6 @@ package com.android.server; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -85,7 +84,6 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { final String typeName = ConnectivityManager.getNetworkTypeName(type); mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities(); mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED); - mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); mNetworkCapabilities.addTransportType(transport); switch (transport) { case TRANSPORT_ETHERNET: diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java index 95a794235a2e..c548e30761c9 100644 --- a/tests/net/java/android/net/VpnManagerTest.java +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -49,7 +49,7 @@ public class VpnManagerTest { private static final String IDENTITY_STRING = "Identity"; private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); - private IConnectivityManager mMockCs; + private IVpnManager mMockService; private VpnManager mVpnManager; private final MockContext mMockContext = new MockContext() { @@ -61,24 +61,26 @@ public class VpnManagerTest { @Before public void setUp() throws Exception { - mMockCs = mock(IConnectivityManager.class); - mVpnManager = new VpnManager(mMockContext, mMockCs); + mMockService = mock(IVpnManager.class); + mVpnManager = new VpnManager(mMockContext, mMockService); } @Test public void testProvisionVpnProfilePreconsented() throws Exception { final PlatformVpnProfile profile = getPlatformVpnProfile(); - when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(true); // Expect there to be no intent returned, as consent has already been granted. assertNull(mVpnManager.provisionVpnProfile(profile)); - verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testProvisionVpnProfileNeedsConsent() throws Exception { final PlatformVpnProfile profile = getPlatformVpnProfile(); - when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(false); // Expect intent to be returned, as consent has not already been granted. final Intent intent = mVpnManager.provisionVpnProfile(profile); @@ -88,25 +90,25 @@ public class VpnManagerTest { ComponentName.unflattenFromString( "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); assertEquals(expectedComponentName, intent.getComponent()); - verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testDeleteProvisionedVpnProfile() throws Exception { mVpnManager.deleteProvisionedVpnProfile(); - verify(mMockCs).deleteVpnProfile(eq(PKG_NAME)); + verify(mMockService).deleteVpnProfile(eq(PKG_NAME)); } @Test public void testStartProvisionedVpnProfile() throws Exception { mVpnManager.startProvisionedVpnProfile(); - verify(mMockCs).startVpnProfile(eq(PKG_NAME)); + verify(mMockService).startVpnProfile(eq(PKG_NAME)); } @Test public void testStopProvisionedVpnProfile() throws Exception { mVpnManager.stopProvisionedVpnProfile(); - verify(mMockCs).stopVpnProfile(eq(PKG_NAME)); + verify(mMockService).stopVpnProfile(eq(PKG_NAME)); } private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java index 2fd5e3861cef..866f38c84333 100644 --- a/tests/net/java/android/net/VpnTransportInfoTest.java +++ b/tests/net/java/android/net/VpnTransportInfoTest.java @@ -17,7 +17,6 @@ package android.net; import static com.android.testutils.ParcelUtils.assertParcelSane; -import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -36,7 +35,6 @@ public class VpnTransportInfoTest { public void testParceling() { VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); assertParcelSane(v, 1 /* fieldCount */); - assertParcelingIsLossless(v); } @Test diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index e639a369eca2..2a693eb94015 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -200,6 +200,7 @@ import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.RouteInfoParcel; import android.net.SocketKeepalive; +import android.net.TransportInfo; import android.net.UidRange; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; @@ -376,6 +377,7 @@ public class ConnectivityServiceTest { private MockContext mServiceContext; private HandlerThread mCsHandlerThread; + private HandlerThread mVMSHandlerThread; private ConnectivityService.Dependencies mDeps; private ConnectivityService mService; private WrappedConnectivityManager mCm; @@ -390,6 +392,7 @@ public class ConnectivityServiceTest { private TestNetIdManager mNetIdManager; private QosCallbackMockHelper mQosCallbackMockHelper; private QosCallbackTracker mQosCallbackTracker; + private VpnManagerService mVpnManagerService; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -1262,24 +1265,55 @@ public class ConnectivityServiceTest { r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new); } - private void mockVpn(int uid) { - synchronized (mService.mVpns) { - int userId = UserHandle.getUserId(uid); - mMockVpn = new MockVpn(userId); - // This has no effect unless the VPN is actually connected, because things like - // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN - // netId, and check if that network is actually connected. - mService.mVpns.put(userId, mMockVpn); - } + private VpnManagerService makeVpnManagerService() { + final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() { + public int getCallingUid() { + return mDeps.getCallingUid(); + } + + public HandlerThread makeHandlerThread() { + return mVMSHandlerThread; + } + + public KeyStore getKeyStore() { + return mKeyStore; + } + + public INetd getNetd() { + return mMockNetd; + } + + public INetworkManagementService getINetworkManagementService() { + return mNetworkManagementService; + } + }; + return new VpnManagerService(mServiceContext, deps); + } + + private void assertVpnTransportInfo(NetworkCapabilities nc, int type) { + assertNotNull(nc); + final TransportInfo ti = nc.getTransportInfo(); + assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti, + ti instanceof VpnTransportInfo); + assertEquals(type, ((VpnTransportInfo) ti).type); + } private void processBroadcastForVpn(Intent intent) { - // The BroadcastReceiver for this broadcast checks it is being run on the handler thread. - final Handler handler = new Handler(mCsHandlerThread.getLooper()); - handler.post(() -> mServiceContext.sendBroadcast(intent)); + mServiceContext.sendBroadcast(intent); + HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS); waitForIdle(); } + private void mockVpn(int uid) { + synchronized (mVpnManagerService.mVpns) { + int userId = UserHandle.getUserId(uid); + mMockVpn = new MockVpn(userId); + // Every running user always has a Vpn in the mVpns array, even if no VPN is running. + mVpnManagerService.mVpns.put(userId, mMockVpn); + } + } + private void mockUidNetworkingBlocked() { doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class) .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules, @@ -1394,6 +1428,7 @@ public class ConnectivityServiceTest { FakeSettingsProvider.clearSettingsProvider(); mServiceContext = new MockContext(InstrumentationRegistry.getContext(), new FakeSettingsProvider()); + mServiceContext.setUseRegisteredHandlers(true); LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService( NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class)); @@ -1403,6 +1438,7 @@ public class ConnectivityServiceTest { initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); mCsHandlerThread = new HandlerThread("TestConnectivityService"); + mVMSHandlerThread = new HandlerThread("TestVpnManagerService"); mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, @@ -1425,6 +1461,8 @@ public class ConnectivityServiceTest { // getSystemService() correctly. mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); mService.systemReadyInternal(); + mVpnManagerService = makeVpnManagerService(); + mVpnManagerService.systemReady(); mockVpn(Process.myUid()); mCm.bindProcessToNetwork(null); mQosCallbackTracker = mock(QosCallbackTracker.class); @@ -1452,7 +1490,6 @@ public class ConnectivityServiceTest { doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); doReturn(mBatteryStatsService).when(deps).getBatteryStatsService(); - doReturn(mKeyStore).when(deps).getKeyStore(); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); @@ -2717,10 +2754,6 @@ public class ConnectivityServiceTest { NetworkCapabilities filter = new NetworkCapabilities(); filter.addCapability(capability); - // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add - // NOT_VCN_MANAGED automatically but not for NetworkCapabilities, - // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details. - filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); handlerThread.start(); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), @@ -3873,6 +3906,24 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(cellNetworkCallback); } + @Test + public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception { + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + final TestNetworkCallback callback = new TestNetworkCallback(); + assertThrows(SecurityException.class, + () -> mCm.registerSystemDefaultNetworkCallback(callback, handler)); + callback.assertNoCallback(); + + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, + PERMISSION_GRANTED); + mCm.registerSystemDefaultNetworkCallback(callback, handler); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); + } + private void setCaptivePortalMode(int mode) { ContentResolver cr = mServiceContext.getContentResolver(); Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode); @@ -4062,7 +4113,6 @@ public class ConnectivityServiceTest { handlerThread.start(); NetworkCapabilities filter = new NetworkCapabilities() .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addCapability(NET_CAPABILITY_INTERNET); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter); @@ -5966,7 +6016,6 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .setLinkDownstreamBandwidthKbps(10); final NetworkCapabilities wifiNc = new NetworkCapabilities() .addTransportType(TRANSPORT_WIFI) @@ -5975,7 +6024,6 @@ public class ConnectivityServiceTest { .addCapability(NET_CAPABILITY_NOT_ROAMING) .addCapability(NET_CAPABILITY_NOT_CONGESTED) .addCapability(NET_CAPABILITY_NOT_SUSPENDED) - .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .setLinkUpstreamBandwidthKbps(20); mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */); mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */); @@ -6484,6 +6532,8 @@ public class ConnectivityServiceTest { assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED)); assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); } private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) { @@ -6523,6 +6573,7 @@ public class ConnectivityServiceTest { assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); // A VPN without underlying networks is not suspended. assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); final int userId = UserHandle.getUserId(Process.myUid()); assertDefaultNetworkCapabilities(userId /* no networks */); @@ -6686,6 +6737,7 @@ public class ConnectivityServiceTest { // By default, VPN is set to track default network (i.e. its underlying networks is null). // In case of no default network, VPN is considered metered. assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); // Connect to Cell; Cell is the default network. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); @@ -6743,6 +6795,7 @@ public class ConnectivityServiceTest { NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertNotNull("nc=" + nc, nc.getUids()); assertEquals(nc.getUids(), uidRangesForUid(uid)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); // Set an underlying network and expect to see the VPN transports change. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); @@ -6825,8 +6878,8 @@ public class ConnectivityServiceTest { // Enable always-on VPN lockdown. The main user loses network access because no VPN is up. final ArrayList<String> allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */, - allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, + true /* lockdown */, allowList); waitForIdle(); assertNull(mCm.getActiveNetworkForUid(uid)); // This is arguably overspecified: a UID that is not running doesn't have an active network. @@ -6856,7 +6909,8 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetworkForUid(uid)); assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, + allowList); waitForIdle(); } @@ -7232,7 +7286,8 @@ public class ConnectivityServiceTest { final int uid = Process.myUid(); final int userId = UserHandle.getUserId(uid); final ArrayList<String> allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1); @@ -7254,7 +7309,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown, expect to see the network unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7267,7 +7322,8 @@ public class ConnectivityServiceTest { // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked. allowList.add(TEST_PACKAGE_NAME); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); callback.assertNoCallback(); defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); @@ -7300,11 +7356,12 @@ public class ConnectivityServiceTest { // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown. // Everything should now be blocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3); allowList.clear(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); @@ -7317,7 +7374,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown. Everything is unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7329,7 +7386,8 @@ public class ConnectivityServiceTest { // Enable and disable an always-on VPN package without lockdown. Expect no changes. reset(mMockNetd); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, + allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7340,7 +7398,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7352,7 +7410,8 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); // Enable lockdown and connect a VPN. The VPN is not blocked. - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7398,11 +7457,14 @@ public class ConnectivityServiceTest { when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); } - private void establishLegacyLockdownVpn() throws Exception { + private void establishLegacyLockdownVpn(Network underlying) throws Exception { + // The legacy lockdown VPN only supports userId 0, and must have an underlying network. + assertNotNull(underlying); mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY); // The legacy lockdown VPN only supports userId 0. final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); mMockVpn.registerAgent(ranges); + mMockVpn.setUnderlyingNetworks(new Network[]{underlying}); mMockVpn.connect(true); } @@ -7410,6 +7472,9 @@ public class ConnectivityServiceTest { public void testLegacyLockdownVpn() throws Exception { mServiceContext.setPermission( Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); + // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); final TestNetworkCallback callback = new TestNetworkCallback(); @@ -7418,6 +7483,10 @@ public class ConnectivityServiceTest { final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + // Pretend lockdown VPN was configured. setupLegacyLockdownVpn(); @@ -7447,6 +7516,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false /* validated */); callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); waitForIdle(); assertNull(mMockVpn.getAgent()); @@ -7458,6 +7528,8 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); waitForIdle(); assertNull(mMockVpn.getAgent()); @@ -7467,6 +7539,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); b1.expectBroadcast(); // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten @@ -7476,6 +7549,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false /* validated */); callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); b1.expectBroadcast(); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); @@ -7498,9 +7572,10 @@ public class ConnectivityServiceTest { mMockVpn.expectStartLegacyVpnRunner(); b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); - establishLegacyLockdownVpn(); + establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork()); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); b1.expectBroadcast(); b2.expectBroadcast(); @@ -7512,9 +7587,7 @@ public class ConnectivityServiceTest { assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); - VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo(); - assertNotNull(ti); - assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type); + assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY); // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. final LinkProperties wifiLp = new LinkProperties(); @@ -7542,11 +7615,10 @@ public class ConnectivityServiceTest { // fact that a VPN is connected should only result in the VPN itself being unblocked, not // any other network. Bug in isUidBlockedByVpn? callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI)); callback.expectCallback(CallbackEntry.LOST, mMockVpn); - defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI)); defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // While the VPN is reconnecting on the new network, everything is blocked. assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7557,9 +7629,10 @@ public class ConnectivityServiceTest { // The VPN comes up again on wifi. b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); - establishLegacyLockdownVpn(); + establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork()); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); b1.expectBroadcast(); b2.expectBroadcast(); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7573,14 +7646,10 @@ public class ConnectivityServiceTest { assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Disconnect cell. Nothing much happens since it's not the default network. - // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any - // NetworkInfo is updated. This is probably a bug. - // TODO: consider fixing this. - b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mCellNetworkAgent.disconnect(); - b1.expectBroadcast(); callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); @@ -7590,6 +7659,7 @@ public class ConnectivityServiceTest { b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); b1.expectBroadcast(); callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI)); b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); @@ -7641,13 +7711,19 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.removeCapability(testCap); callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); + // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has + // it. + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + } mCellNetworkAgent.removeCapability(testCap); callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); callbackWithoutCap.assertNoCallback(); - verify(mMockNetd).networkClearDefault(); + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkClearDefault(); + } mCm.unregisterNetworkCallback(callbackWithCap); mCm.unregisterNetworkCallback(callbackWithoutCap); diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index 799bcc82d2a9..c86224a71978 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.INetd; import android.net.InetAddresses; import android.net.IpSecAlgorithm; @@ -44,6 +45,7 @@ import android.net.IpSecTransformResponse; import android.net.IpSecTunnelInterfaceResponse; import android.net.IpSecUdpEncapResponse; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.os.Binder; import android.os.INetworkManagementService; @@ -53,6 +55,8 @@ import android.test.mock.MockContext; import androidx.test.filters.SmallTest; +import com.android.server.IpSecService.TunnelInterfaceRecord; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -109,6 +113,7 @@ public class IpSecServiceParameterizedTest { }; AppOpsManager mMockAppOps = mock(AppOpsManager.class); + ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class); MockContext mMockContext = new MockContext() { @Override @@ -116,12 +121,22 @@ public class IpSecServiceParameterizedTest { switch(name) { case Context.APP_OPS_SERVICE: return mMockAppOps; + case Context.CONNECTIVITY_SERVICE: + return mMockConnectivityMgr; default: return null; } } @Override + public String getSystemServiceName(Class<?> serviceClass) { + if (ConnectivityManager.class == serviceClass) { + return Context.CONNECTIVITY_SERVICE; + } + return null; + } + + @Override public PackageManager getPackageManager() { return mMockPkgMgr; } @@ -151,6 +166,10 @@ public class IpSecServiceParameterizedTest { new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); private static final int REMOTE_ENCAP_PORT = 4500; + private static final String BLESSED_PACKAGE = "blessedPackage"; + private static final String SYSTEM_PACKAGE = "systemPackage"; + private static final String BAD_PACKAGE = "badPackage"; + public IpSecServiceParameterizedTest( String sourceAddr, String destAddr, String localInnerAddr, int family) { mSourceAddr = sourceAddr; @@ -174,15 +193,15 @@ public class IpSecServiceParameterizedTest { when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true); // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED. - when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage"))) - .thenReturn(AppOpsManager.MODE_ALLOWED); + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE))) + .thenReturn(AppOpsManager.MODE_ALLOWED); // A system package will not be granted the app op, so this should fall back to // a permissions check, which should pass. - when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("systemPackage"))) - .thenReturn(AppOpsManager.MODE_DEFAULT); + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE))) + .thenReturn(AppOpsManager.MODE_DEFAULT); // A mismatch between the package name and the UID will return MODE_IGNORED. - when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("badPackage"))) - .thenReturn(AppOpsManager.MODE_IGNORED); + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE))) + .thenReturn(AppOpsManager.MODE_IGNORED); } //TODO: Add a test to verify SPI. @@ -338,7 +357,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); @@ -352,7 +371,7 @@ public class IpSecServiceParameterizedTest { ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); @@ -370,14 +389,14 @@ public class IpSecServiceParameterizedTest { if (mFamily == AF_INET) { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); } else { try { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); } catch (IllegalArgumentException expected) { } @@ -396,14 +415,14 @@ public class IpSecServiceParameterizedTest { if (mFamily == AF_INET) { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); } else { try { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); } catch (IllegalArgumentException expected) { } @@ -417,12 +436,12 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); // Attempting to create transform a second time with the same SPIs should throw an error... try { - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("IpSecService should have thrown an error for reuse of SPI"); } catch (IllegalStateException expected) { } @@ -430,7 +449,7 @@ public class IpSecServiceParameterizedTest { // ... even if the transform is deleted mIpSecService.deleteTransform(createTransformResp.resourceId); try { - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("IpSecService should have thrown an error for reuse of SPI"); } catch (IllegalStateException expected) { } @@ -443,7 +462,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -467,7 +486,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); mIpSecService.deleteTransform(createTransformResp.resourceId); verify(mMockNetd, times(1)) @@ -515,7 +534,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); IpSecService.RefcountedResource refcountedRecord = @@ -562,7 +581,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); if (closeSpiBeforeApply) { mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -592,7 +611,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); // Close SPI record mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -638,7 +657,7 @@ public class IpSecServiceParameterizedTest { @Test public void testCreateTunnelInterface() throws Exception { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); // Check that we have stored the tracking object, and retrieve it IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); @@ -661,11 +680,11 @@ public class IpSecServiceParameterizedTest { @Test public void testDeleteTunnelInterface() throws Exception { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); - mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage"); + mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE); // Verify quota and RefcountedResource objects cleaned up assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent); @@ -678,10 +697,73 @@ public class IpSecServiceParameterizedTest { } } + private Network createFakeUnderlyingNetwork(String interfaceName) { + final Network fakeNetwork = new Network(1000); + final LinkProperties fakeLp = new LinkProperties(); + fakeLp.setInterfaceName(interfaceName); + when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp); + return fakeNetwork; + } + + @Test + public void testSetNetworkForTunnelInterface() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface"); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent); + + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork()); + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = new Network(1000); + + try { + mIpSecService.setNetworkForTunnelInterface( + IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE); + fail("Expected an IllegalArgumentException for invalid resource ID."); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + + final Network newFakeNetwork = + createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName()); + + try { + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + fail( + "Expected an IllegalArgumentException because the underlying network is the" + + " network being exposed by this tunnel."); + } catch (IllegalArgumentException expected) { + } + } + @Test public void testTunnelInterfaceBinderDeath() throws Exception { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); IpSecService.RefcountedResource refcountedRecord = @@ -718,9 +800,9 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); if (closeSpiBeforeApply) { mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -728,8 +810,8 @@ public class IpSecServiceParameterizedTest { int transformResourceId = createTransformResp.resourceId; int tunnelResourceId = createTunnelResp.resourceId; - mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT, - transformResourceId, "blessedPackage"); + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE); for (int selAddrFamily : ADDRESS_FAMILIES) { verify(mMockNetd) @@ -758,17 +840,17 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); // Close SPI record mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); int transformResourceId = createTransformResp.resourceId; int tunnelResourceId = createTunnelResp.resourceId; - mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT, - transformResourceId, "blessedPackage"); + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE); for (int selAddrFamily : ADDRESS_FAMILIES) { verify(mMockNetd) @@ -790,7 +872,7 @@ public class IpSecServiceParameterizedTest { @Test public void testAddRemoveAddressFromTunnelInterface() throws Exception { - for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) { + for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) { IpSecTunnelInterfaceResponse createTunnelResp = createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName); mIpSecService.addAddressToTunnelInterface( @@ -816,7 +898,7 @@ public class IpSecServiceParameterizedTest { public void testAddTunnelFailsForBadPackageName() throws Exception { try { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "badPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE); fail("Expected a SecurityException for badPackage."); } catch (SecurityException expected) { } @@ -830,7 +912,7 @@ public class IpSecServiceParameterizedTest { try { String addr = Inet4Address.getLoopbackAddress().getHostAddress(); mIpSecService.createTunnelInterface( - addr, addr, new Network(0), new Binder(), "blessedPackage"); + addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE); fail("Expected UnsupportedOperationException for disabled feature"); } catch (UnsupportedOperationException expected) { } diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index 1102624da031..4a1f96d145bd 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java @@ -42,6 +42,8 @@ public class BroadcastInterceptingContext extends ContextWrapper { private final List<BroadcastInterceptor> mInterceptors = new ArrayList<>(); + private boolean mUseRegisteredHandlers; + public abstract class FutureIntent extends FutureTask<Intent> { public FutureIntent() { super( @@ -61,17 +63,24 @@ public class BroadcastInterceptingContext extends ContextWrapper { public class BroadcastInterceptor extends FutureIntent { private final BroadcastReceiver mReceiver; private final IntentFilter mFilter; + private final Handler mHandler; - public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) { + public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter, + Handler handler) { mReceiver = receiver; mFilter = filter; + mHandler = mUseRegisteredHandlers ? handler : null; } public boolean dispatchBroadcast(Intent intent) { if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) { if (mReceiver != null) { final Context context = BroadcastInterceptingContext.this; - mReceiver.onReceive(context, intent); + if (mHandler == null) { + mReceiver.onReceive(context, intent); + } else { + mHandler.post(() -> mReceiver.onReceive(context, intent)); + } return false; } else { set(intent); @@ -116,25 +125,38 @@ public class BroadcastInterceptingContext extends ContextWrapper { } public FutureIntent nextBroadcastIntent(IntentFilter filter) { - final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter); + final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter, null); synchronized (mInterceptors) { mInterceptors.add(interceptor); } return interceptor; } - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + /** + * Whether to send broadcasts to registered handlers. By default, receivers are called + * synchronously by sendBroadcast. If this method is called with {@code true}, the receiver is + * instead called by a runnable posted to the Handler specified when the receiver was + * registered. This method applies only to future registrations, already-registered receivers + * are unaffected. + */ + public void setUseRegisteredHandlers(boolean use) { synchronized (mInterceptors) { - mInterceptors.add(new BroadcastInterceptor(receiver, filter)); + mUseRegisteredHandlers = use; } - return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return registerReceiver(receiver, filter, null, null); } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { - return registerReceiver(receiver, filter); + synchronized (mInterceptors) { + mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler)); + } + return null; } @Override diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 278d93a1b17b..faa8b234cf51 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -81,6 +81,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); verify(mIkeSession, never()).close(); + verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -170,6 +171,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -179,5 +181,6 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); + verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index d936183e5a0b..760379d6649d 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -61,6 +61,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verify(mIkeSession).kill(); + verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -73,6 +74,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); verify(mIkeSession, never()).kill(); + verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -92,6 +94,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); + verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -101,5 +104,6 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); + verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 8643d8a2ea8a..1a1787ac19d9 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -16,12 +16,18 @@ package com.android.server.vcn; +import static android.net.IpSecManager.IpSecTunnelInterface; + +import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import android.net.IpSecManager; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -37,6 +43,11 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect public void setUp() throws Exception { super.setUp(); + final IpSecTunnelInterface tunnelIface = + mContext.getSystemService(IpSecManager.class) + .createIpSecTunnelInterface( + DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network); + mGatewayConnection.setTunnelInterface(tunnelIface); mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState); mTestLooper.dispatchAll(); } @@ -77,6 +88,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); } @Test diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java index d0fec55a6827..b09f3a008c29 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -16,9 +16,9 @@ package com.android.server.vcn; -import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import androidx.test.filters.SmallTest; @@ -28,8 +28,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.TimeUnit; - /** Tests for VcnGatewayConnection.DisconnectedState */ @RunWith(AndroidJUnit4.class) @SmallTest @@ -40,6 +38,9 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession()); + // ensure that mGatewayConnection has an underlying Network before entering + // DisconnectingState + mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_2); mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState); mTestLooper.dispatchAll(); } @@ -49,12 +50,22 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec getIkeSessionCallback().onClosed(); mTestLooper.dispatchAll(); - assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); + verify(mMockIkeSession).close(); + verify(mMockIkeSession, never()).kill(); + verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */); } @Test public void testTimeoutExpired() throws Exception { - mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + Runnable delayedEvent = + verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); + + // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages + // (which are mocked here). Directly invoke the runnable instead. This is still sufficient, + // since verifyTeardownTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled + // with the correct delay. + delayedEvent.run(); mTestLooper.dispatchAll(); verify(mMockIkeSession).kill(); @@ -67,5 +78,6 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec // Should do nothing; already tearing down. assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index 3f2b47cd58fd..d37e92f661cc 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -29,10 +29,14 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase { + private long mFirstRetryInterval; + @Before public void setUp() throws Exception { super.setUp(); + mFirstRetryInterval = mConfig.getRetryInterval()[0]; + mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState); mTestLooper.dispatchAll(); @@ -46,6 +50,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */); } @Test @@ -56,6 +61,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); + verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, false /* expectCanceled */); } @Test @@ -66,13 +72,23 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */); } @Test public void testTimeoutElapsingTriggersRetry() throws Exception { - mTestLooper.moveTimeForward(mConfig.getRetryIntervalsMs()[0]); + final Runnable delayedEvent = + verifyRetryTimeoutAlarmAndGetCallback( + mFirstRetryInterval, false /* expectCanceled */); + + // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages + // (which are mocked here). Directly invoke the runnable instead. This is still sufficient, + // since verifyRetryTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled + // with the correct delay. + delayedEvent.run(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index bc6bee28d14f..748c7924685d 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -132,10 +132,32 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { @Test public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() { + verifyWakeLockSetUp(); + final TelephonySubscriptionSnapshot updatedSnapshot = mock(TelephonySubscriptionSnapshot.class); mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot); verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot)); + verifyWakeLockAcquired(); + + mTestLooper.dispatchAll(); + + verifyWakeLockReleased(); + } + + @Test + public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + + verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); + + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); + + verify(mDisconnectRequestAlarm).cancel(); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index d449eab494f6..0b44e03a5e5a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -21,9 +21,14 @@ import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.annotation.NonNull; import android.content.Context; @@ -42,12 +47,15 @@ import android.net.ipsec.ike.IkeSessionCallback; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; +import android.os.PowerManager; import android.os.test.TestLooper; +import com.android.internal.util.WakeupMessage; import com.android.server.IpSecService; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback; +import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -55,6 +63,7 @@ import org.mockito.ArgumentCaptor; import java.net.InetAddress; import java.util.Collections; import java.util.UUID; +import java.util.concurrent.TimeUnit; public class VcnGatewayConnectionTestBase { protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID()); @@ -68,6 +77,7 @@ public class VcnGatewayConnectionTestBase { protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2; protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3; protected static final int TEST_SUB_ID = 5; + protected static final long ELAPSED_REAL_TIME = 123456789L; protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = new UnderlyingNetworkRecord( @@ -94,6 +104,10 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull protected final VcnGatewayConnection.Dependencies mDeps; @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + @NonNull protected final VcnWakeLock mWakeLock; + @NonNull protected final WakeupMessage mTeardownTimeoutAlarm; + @NonNull protected final WakeupMessage mDisconnectRequestAlarm; + @NonNull protected final WakeupMessage mRetryTimeoutAlarm; @NonNull protected final IpSecService mIpSecSvc; @NonNull protected final ConnectivityManager mConnMgr; @@ -110,6 +124,10 @@ public class VcnGatewayConnectionTestBase { mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class); mDeps = mock(VcnGatewayConnection.Dependencies.class); mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); + mWakeLock = mock(VcnWakeLock.class); + mTeardownTimeoutAlarm = mock(WakeupMessage.class); + mDisconnectRequestAlarm = mock(WakeupMessage.class); + mRetryTimeoutAlarm = mock(WakeupMessage.class); mIpSecSvc = mock(IpSecService.class); setupIpSecManager(mContext, mIpSecSvc); @@ -125,6 +143,19 @@ public class VcnGatewayConnectionTestBase { doReturn(mUnderlyingNetworkTracker) .when(mDeps) .newUnderlyingNetworkTracker(any(), any(), any(), any(), any()); + doReturn(mWakeLock) + .when(mDeps) + .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); + + setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM); + setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM); + setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM); + + doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime(); + } + + private void setUpWakeupMessage(@NonNull WakeupMessage msg, @NonNull String cmdName) { + doReturn(msg).when(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any()); } @Before @@ -166,4 +197,60 @@ public class VcnGatewayConnectionTestBase { verify(mDeps).newIkeSession(any(), any(), any(), any(), captor.capture()); return (VcnChildSessionCallback) captor.getValue(); } + + protected void verifyWakeLockSetUp() { + verify(mDeps).newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); + verifyNoMoreInteractions(mWakeLock); + } + + protected void verifyWakeLockAcquired() { + verify(mWakeLock).acquire(); + verifyNoMoreInteractions(mWakeLock); + } + + protected void verifyWakeLockReleased() { + verify(mWakeLock).release(); + verifyNoMoreInteractions(mWakeLock); + } + + private Runnable verifyWakeupMessageSetUpAndGetCallback( + @NonNull String tag, + @NonNull WakeupMessage msg, + long delayInMillis, + boolean expectCanceled) { + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(tag), runnableCaptor.capture()); + + verify(mDeps, atLeastOnce()).getElapsedRealTime(); + verify(msg).schedule(ELAPSED_REAL_TIME + delayInMillis); + verify(msg, expectCanceled ? times(1) : never()).cancel(); + + return runnableCaptor.getValue(); + } + + protected Runnable verifyTeardownTimeoutAlarmAndGetCallback(boolean expectCanceled) { + return verifyWakeupMessageSetUpAndGetCallback( + VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM, + mTeardownTimeoutAlarm, + TimeUnit.SECONDS.toMillis(VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS), + expectCanceled); + } + + protected Runnable verifyDisconnectRequestAlarmAndGetCallback(boolean expectCanceled) { + return verifyWakeupMessageSetUpAndGetCallback( + VcnGatewayConnection.DISCONNECT_REQUEST_ALARM, + mDisconnectRequestAlarm, + TimeUnit.SECONDS.toMillis( + VcnGatewayConnection.NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS), + expectCanceled); + } + + protected Runnable verifyRetryTimeoutAlarmAndGetCallback( + long delayInMillis, boolean expectCanceled) { + return verifyWakeupMessageSetUpAndGetCallback( + VcnGatewayConnection.RETRY_TIMEOUT_ALARM, + mRetryTimeoutAlarm, + delayInMillis, + expectCanceled); + } } |