diff options
388 files changed, 10156 insertions, 6579 deletions
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 271129bee7c5..c369801a091f 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -108,7 +108,7 @@ public class AppSearchManagerService extends SystemService { schemasPackageAccessibleBundles.entrySet()) { List<PackageIdentifier> packageIdentifiers = new ArrayList<>(entry.getValue().size()); - for (int i = 0; i < packageIdentifiers.size(); i++) { + for (int i = 0; i < entry.getValue().size(); i++) { packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i))); } schemasPackageAccessible.put(entry.getKey(), packageIdentifiers); 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/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java index 8bff7207a553..2f1817ec82a7 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java @@ -317,7 +317,7 @@ public final class AppSearchImpl { } Map<String, List<PackageIdentifier>> prefixedSchemasPackageAccessible = - new ArrayMap<>(schemasNotPlatformSurfaceable.size()); + new ArrayMap<>(schemasPackageAccessible.size()); for (Map.Entry<String, List<PackageIdentifier>> entry : schemasPackageAccessible.entrySet()) { prefixedSchemasPackageAccessible.put(prefix + entry.getKey(), entry.getValue()); diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java index eb1623e5dffc..6595d8d4abba 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java @@ -37,8 +37,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via - * a consistent interface. + * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a + * consistent interface. + * * @hide */ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { @@ -47,7 +48,13 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { @NonNull public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() { - Context context = ApplicationProvider.getApplicationContext(); + return createGlobalSearchSession(ApplicationProvider.getApplicationContext()); + } + + /** Only for use when called from a non-instrumented context. */ + @NonNull + public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession( + @NonNull Context context) { AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class); SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create(); ExecutorService executor = Executors.newCachedThreadPool(); @@ -62,7 +69,6 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) { mGlobalSearchSession = Preconditions.checkNotNull(session); mExecutor = Preconditions.checkNotNull(executor); - } @NonNull 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 82ee5d8c1c7c..71fe55fb0640 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -212,7 +212,7 @@ public class JobSchedulerService extends com.android.server.SystemService final JobPackageTracker mJobPackageTracker = new JobPackageTracker(); final JobConcurrencyManager mConcurrencyManager; - static final int MSG_JOB_EXPIRED = 0; + static final int MSG_CHECK_INDIVIDUAL_JOB = 0; static final int MSG_CHECK_JOB = 1; static final int MSG_STOP_JOB = 2; static final int MSG_CHECK_JOB_GREEDY = 3; @@ -1711,6 +1711,12 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); } + JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId()); + if (newJs != null) { + // This job was stopped because the app scheduled a new job with the same job ID. + // Check if the new job is ready to run. + mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget(); + } return; } @@ -1748,7 +1754,11 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public void onRunJobNow(JobStatus jobStatus) { - mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget(); + if (jobStatus == null) { + mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); + } else { + mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget(); + } } final private class JobHandler extends Handler { @@ -1764,18 +1774,15 @@ public class JobSchedulerService extends com.android.server.SystemService return; } switch (message.what) { - case MSG_JOB_EXPIRED: { - JobStatus runNow = (JobStatus) message.obj; - // runNow can be null, which is a controller's way of indicating that its - // state is such that all ready jobs should be run immediately. - if (runNow != null) { - if (!isCurrentlyActiveLocked(runNow) - && isReadyToBeExecutedLocked(runNow)) { - mJobPackageTracker.notePending(runNow); - addOrderedItem(mPendingJobs, runNow, sPendingJobComparator); + case MSG_CHECK_INDIVIDUAL_JOB: { + JobStatus js = (JobStatus) message.obj; + if (js != null) { + if (isReadyToBeExecutedLocked(js)) { + mJobPackageTracker.notePending(js); + addOrderedItem(mPendingJobs, js, sPendingJobComparator); } } else { - queueReadyJobsForExecutionLocked(); + Slog.e(TAG, "Given null job to check individually"); } } break; case MSG_CHECK_JOB: @@ -1909,12 +1916,10 @@ public class JobSchedulerService extends com.android.server.SystemService // This method will check and capture all ready jobs, so we don't need to keep any messages // in the queue. mHandler.removeMessages(MSG_CHECK_JOB_GREEDY); + mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB); // MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready // jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue. mHandler.removeMessages(MSG_CHECK_JOB); - // This method will capture all expired jobs that are ready, so there's no need to keep - // the _EXPIRED messages in the queue. - mHandler.removeMessages(MSG_JOB_EXPIRED); if (DEBUG) { Slog.d(TAG, "queuing all ready jobs for execution:"); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java index 50723c7c2841..131a6d4f4791 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java @@ -107,8 +107,6 @@ public final class ContentObserverController extends StateController { taskStatus.contentObserverJobInstance.mChangedUris.add(uri); } } - taskStatus.changedAuthorities = null; - taskStatus.changedUris = null; } taskStatus.changedAuthorities = null; taskStatus.changedUris = null; 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/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 4b7eda096e54..ed717c491467 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -19,7 +19,6 @@ 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; @@ -667,7 +666,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, OperationType.BACKUP); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; @@ -822,7 +821,7 @@ public class Bmgr { try { boolean didRestore = false; - mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index 312f4acbda9f..05336baf9217 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -161,6 +161,12 @@ Status Idmap2Service::createIdmap(const std::string& target_path, const std::str idmap_path.c_str(), uid)); } + // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees + // that existing memory maps will continue to be valid and unaffected. The file must be deleted + // before attempting to create the idmap, so that if idmap creation fails, the overlay will no + // longer be usable. + unlink(idmap_path.c_str()); + const auto target = GetTargetContainer(target_path); if (!target) { return error("failed to load target '%s'" + target_path); @@ -177,10 +183,6 @@ Status Idmap2Service::createIdmap(const std::string& target_path, const std::str return error(idmap.GetErrorMessage()); } - // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees - // that existing memory maps will continue to be valid and unaffected. - unlink(idmap_path.c_str()); - umask(kIdmapFilePermissionMask); std::ofstream fout(idmap_path); if (fout.fail()) { diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt index 90a526bcfaf7..48aa8b2c2e30 100644 --- a/config/hiddenapi-unsupported.txt +++ b/config/hiddenapi-unsupported.txt @@ -204,7 +204,6 @@ Landroid/os/IRemoteCallback$Stub;-><init>()V Landroid/os/IUpdateEngine$Stub;-><init>()V Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/IUserManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IUserManager; -Landroid/os/IVibratorService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IVibratorService; Landroid/os/storage/IObbActionListener$Stub;-><init>()V Landroid/os/storage/IStorageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/storage/IStorageManager; diff --git a/core/api/current.txt b/core/api/current.txt index 53be53c62786..ac88e5bd1dc0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -849,6 +849,7 @@ package android { field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c + field public static final int knownCerts = 16844330; // 0x101062a field public static final int label = 16842753; // 0x1010001 field public static final int labelFor = 16843718; // 0x10103c6 field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235 @@ -6895,6 +6896,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); @@ -7071,6 +7073,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(); @@ -7299,6 +7302,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 @@ -7335,7 +7339,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 @@ -7489,7 +7492,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; } @@ -10440,6 +10443,7 @@ package android.content { field public static final String USAGE_STATS_SERVICE = "usagestats"; field public static final String USB_SERVICE = "usb"; field public static final String USER_SERVICE = "user"; + field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager"; field public static final String VIBRATOR_SERVICE = "vibrator"; field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management"; field public static final String WALLPAPER_SERVICE = "wallpaper"; @@ -10980,6 +10984,7 @@ package android.content { field public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME"; field public static final String EXTRA_INTENT = "android.intent.extra.INTENT"; + field public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED"; field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT"; field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID"; @@ -23033,10 +23038,12 @@ package android.media { method @Deprecated public int getStreamType(); method public String getTitle(android.content.Context); method public float getVolume(); + method public boolean isHapticGeneratorEnabled(); method public boolean isLooping(); method public boolean isPlaying(); method public void play(); method public void setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException; + method public boolean setHapticGeneratorEnabled(boolean); method public void setLooping(boolean); method @Deprecated public void setStreamType(int); method public void setVolume(float); @@ -31625,6 +31632,7 @@ package android.os { method @NonNull public int[] areEffectsSupported(@NonNull int...); method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...); method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); + method public int getId(); method public abstract boolean hasAmplitudeControl(); method public abstract boolean hasVibrator(); method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long); @@ -31639,10 +31647,12 @@ package android.os { } public abstract class VibratorManager { + method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); method @NonNull public abstract android.os.Vibrator getDefaultVibrator(); method @NonNull public abstract android.os.Vibrator getVibrator(int); method @NonNull public abstract int[] getVibratorIds(); - method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect); + method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect); + method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes); } public class WorkSource implements android.os.Parcelable { @@ -42029,8 +42039,9 @@ package android.telephony { field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2 field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1 field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0 + field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4 field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3 - field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4 + field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4 } public class TelephonyManager { @@ -53582,16 +53593,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); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index dffa0cc315ae..fb6cfeaf2437 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -179,6 +179,7 @@ package android { field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD"; field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS"; field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING"; + field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"; field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION"; field public static final String POWER_SAVER = "android.permission.POWER_SAVER"; field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; @@ -338,9 +339,8 @@ package android { field public static final int config_helpPackageNameKey = 17039387; // 0x104001b field public static final int config_helpPackageNameValue = 17039388; // 0x104001c field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028 - field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a + field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029 field public static final int config_systemGallery = 17039399; // 0x1040027 - field public static final int config_systemVideoCall = 17039401; // 0x1040029 } public static final class R.style { @@ -2632,6 +2632,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000 field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000 field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000 + field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000 field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000 field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000 field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000 @@ -9518,10 +9519,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); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 5d0cea56a2d4..694507db6fe7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -54,9 +54,8 @@ package android { field public static final int config_defaultAssistant = 17039393; // 0x1040021 field public static final int config_defaultDialer = 17039395; // 0x1040023 field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028 - field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a + field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029 field public static final int config_systemGallery = 17039399; // 0x1040027 - field public static final int config_systemVideoCall = 17039401; // 0x1040029 } } @@ -392,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 @@ -426,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 @@ -461,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 { @@ -694,6 +693,7 @@ package android.content.pm { method @Nullable public String getSystemTextClassifierPackageName(); method @Nullable public String getWellbeingPackageName(); method public void holdLock(android.os.IBinder, int); + method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>); field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec"; @@ -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 { @@ -1372,6 +1374,32 @@ package android.os { field public static final int RESOURCES_SDK_INT; } + public abstract class CombinedVibrationEffect implements android.os.Parcelable { + method public abstract long getDuration(); + } + + public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect { + method public long getDuration(); + method @NonNull public android.os.VibrationEffect getEffect(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR; + } + + public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect { + method @NonNull public java.util.List<java.lang.Integer> getDelays(); + method public long getDuration(); + method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR; + } + + public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect { + method public long getDuration(); + method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR; + } + public class DeviceIdleManager { method @NonNull public String[] getSystemPowerWhitelist(); method @NonNull public String[] getSystemPowerWhitelistExceptIdle(); 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/Notification.java b/core/java/android/app/Notification.java index c242fd466c41..050f34a21477 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -9972,6 +9972,15 @@ public class Notification implements Parcelable * <p>The shortcut activity will be used when the bubble is expanded. This will display * the shortcut activity in a floating window over the existing foreground activity.</p> * + * <p>When the shortcut is displayed in a bubble, there will be an intent + * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED} + * with {@code true}. You may check this in the onCreate of your activity via: + * + * <pre class="prettyprint"> + * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false); + * </pre> + * </p> + * * <p>If the shortcut has not been published when the bubble notification is sent, * no bubble will be produced. If the shortcut is deleted while the bubble is active, * the bubble will be removed.</p> @@ -10000,6 +10009,15 @@ public class Notification implements Parcelable * app content in a floating window over the existing foreground activity. The intent * should point to a resizable activity. </p> * + * <p>When the activity is displayed in a bubble, there will be an intent + * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED} + * with {@code true}. You may check this in the onCreate of your activity via: + * + * <pre class="prettyprint"> + * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false); + * </pre> + * </p> + * * @throws NullPointerException if intent is null. * @throws NullPointerException if icon is null. */ diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 7404e53bd8b3..d5e95708a805 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -167,9 +167,11 @@ import android.os.StatsFrameworkInitializer; import android.os.SystemConfigManager; import android.os.SystemUpdateManager; import android.os.SystemVibrator; +import android.os.SystemVibratorManager; import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; +import android.os.VibratorManager; import android.os.health.SystemHealthManager; import android.os.image.DynamicSystemManager; import android.os.image.IDynamicSystemService; @@ -699,6 +701,13 @@ public final class SystemServiceRegistry { } }); + registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class, + new CachedServiceFetcher<VibratorManager>() { + @Override + public VibratorManager createService(ContextImpl ctx) { + return new SystemVibratorManager(ctx); + }}); + registerService(Context.VIBRATOR_SERVICE, Vibrator.class, new CachedServiceFetcher<Vibrator>() { @Override 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..e95ab17da09b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2922,33 +2922,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 */ @@ -13157,7 +13185,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); 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/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 673de8fa7c8c..dae565e12fd7 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -361,36 +361,7 @@ public class BackupManager { try { // All packages, current transport IRestoreSession binder = - 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); + sService.beginRestoreSessionForUser(mContext.getUserId(), null, null); if (binder != null) { session = new RestoreSession(mContext, binder); } @@ -801,7 +772,7 @@ public class BackupManager { @SystemApi @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[] packages, BackupObserver observer) { - return requestBackup(packages, observer, null, 0, OperationType.BACKUP); + return requestBackup(packages, observer, null, 0); } /** @@ -826,31 +797,6 @@ 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 { @@ -860,8 +806,7 @@ public class BackupManager { BackupManagerMonitorWrapper monitorWrapper = monitor == null ? null : new BackupManagerMonitorWrapper(monitor); - return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags, - operationType); + return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags); } catch (RemoteException e) { Log.e(TAG, "requestBackup() couldn't connect"); } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index e1bbc08e72f3..bf5be95c4ab0 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -547,11 +547,9 @@ 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, - int operationType); + IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID); /** * Notify the backup manager that a BackupAgent has completed the operation @@ -680,7 +678,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 operationType); + int flags); /** * Cancel all running backups. After this call returns, no currently running backups will diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/time/ExternalTimeSuggestion.aidl index 14d57bf463fb..07a0fbbcbe8e 100644 --- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl +++ b/core/java/android/app/time/ExternalTimeSuggestion.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.app.timedetector; +package android.app.time; parcelable ExternalTimeSuggestion; diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java index 7ad303a9a1c6..b566eab9867d 100644 --- a/core/java/android/app/timedetector/ExternalTimeSuggestion.java +++ b/core/java/android/app/time/ExternalTimeSuggestion.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.timedetector; +package android.app.time; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index 462654323cb4..c4546be10601 100644 --- a/core/java/android/app/timedetector/ITimeDetectorService.aidl +++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl @@ -16,7 +16,7 @@ package android.app.timedetector; -import android.app.timedetector.ExternalTimeSuggestion; +import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index df8d797e5b90..76f378590ae2 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -19,6 +19,7 @@ package android.app.timedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.app.time.ExternalTimeSuggestion; import android.content.Context; import android.os.SystemClock; import android.os.TimestampedValue; diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java index f80869f0a4fc..ef818ef647d6 100644 --- a/core/java/android/app/timedetector/TimeDetectorImpl.java +++ b/core/java/android/app/timedetector/TimeDetectorImpl.java @@ -17,6 +17,7 @@ package android.app.timedetector; import android.annotation.NonNull; +import android.app.time.ExternalTimeSuggestion; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2a402b204cb7..025d777f2053 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3479,6 +3479,7 @@ public abstract class Context { STORAGE_STATS_SERVICE, WALLPAPER_SERVICE, TIME_ZONE_RULES_MANAGER_SERVICE, + VIBRATOR_MANAGER_SERVICE, VIBRATOR_SERVICE, //@hide: STATUS_BAR_SERVICE, CONNECTIVITY_SERVICE, @@ -3625,9 +3626,11 @@ public abstract class Context { * (e.g., GPS) updates. * <dt> {@link #SEARCH_SERVICE} ("search") * <dd> A {@link android.app.SearchManager} for handling search. + * <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager") + * <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting + * with individual ones and playing synchronized effects on multiple vibrators. * <dt> {@link #VIBRATOR_SERVICE} ("vibrator") - * <dd> A {@link android.os.Vibrator} for interacting with the vibrator - * hardware. + * <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware. * <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity") * <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for * handling management of network connections. @@ -3707,6 +3710,8 @@ public abstract class Context { * @see android.hardware.SensorManager * @see #STORAGE_SERVICE * @see android.os.storage.StorageManager + * @see #VIBRATOR_MANAGER_SERVICE + * @see android.os.VibratorManager * @see #VIBRATOR_SERVICE * @see android.os.Vibrator * @see #CONNECTIVITY_SERVICE @@ -4034,8 +4039,19 @@ public abstract class Context { public static final String WALLPAPER_SERVICE = "wallpaper"; /** - * Use with {@link #getSystemService(String)} to retrieve a {@link - * android.os.Vibrator} for interacting with the vibration hardware. + * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager} + * for accessing the device vibrators, interacting with individual ones and playing synchronized + * effects on multiple vibrators. + * + * @see #getSystemService(String) + * @see android.os.VibratorManager + */ + @SuppressLint("ServiceName") + public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for + * interacting with the vibration hardware. * * @see #getSystemService(String) * @see android.os.Vibrator diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 30b24044a624..4abd8cd7d37b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5718,7 +5718,7 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT"; /** - * Used in the extra field in the remote intent. It's astring token passed with the + * Used in the extra field in the remote intent. It's a string token passed with the * remote intent. */ public static final String EXTRA_REMOTE_INTENT_TOKEN = @@ -6062,6 +6062,16 @@ public class Intent implements Parcelable, Cloneable { */ public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON"; + /** + * A boolean extra indicating whether an activity is bubbled. Set on the shortcut or + * pending intent provided for the bubble. If the extra is not present or false, then it is not + * bubbled. + * + * @see android.app.Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata) + * @see android.app.Notification.BubbleMetadata.Builder#Builder(String) + */ + public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/java/android/content/pm/DataLoaderManager.java b/core/java/android/content/pm/DataLoaderManager.java index e8fb2413bbbe..4d79936a8b22 100644 --- a/core/java/android/content/pm/DataLoaderManager.java +++ b/core/java/android/content/pm/DataLoaderManager.java @@ -41,6 +41,7 @@ public class DataLoaderManager { * @param dataLoaderId ID for the new data loader binder service. * @param params DataLoaderParamsParcel object that contains data loader params, including * its package name, class name, and additional parameters. + * @param bindDelayMs introduce a delay before actual bind in case we want to avoid busylooping * @param listener Callback for the data loader service to report status back to the * caller. * @return false if 1) target ID collides with a data loader that is already bound to data @@ -48,9 +49,9 @@ public class DataLoaderManager { * or 4) fails to bind to the specified data loader service, otherwise return true. */ public boolean bindToDataLoader(int dataLoaderId, @NonNull DataLoaderParamsParcel params, - @NonNull IDataLoaderStatusListener listener) { + long bindDelayMs, @NonNull IDataLoaderStatusListener listener) { try { - return mService.bindToDataLoader(dataLoaderId, params, listener); + return mService.bindToDataLoader(dataLoaderId, params, bindDelayMs, listener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/IDataLoaderManager.aidl b/core/java/android/content/pm/IDataLoaderManager.aidl index 93b3de7897c4..dda4d36d1217 100644 --- a/core/java/android/content/pm/IDataLoaderManager.aidl +++ b/core/java/android/content/pm/IDataLoaderManager.aidl @@ -23,7 +23,7 @@ import android.content.pm.IDataLoaderStatusListener; /** @hide */ interface IDataLoaderManager { - boolean bindToDataLoader(int id, in DataLoaderParamsParcel params, + boolean bindToDataLoader(int id, in DataLoaderParamsParcel params, long bindDelayMs, IDataLoaderStatusListener listener); IDataLoader getDataLoader(int dataLoaderId); void unbindFromDataLoader(int dataLoaderId); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index a46876ec53c4..7fe2a41cc4b9 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -808,4 +808,6 @@ interface IPackageManager { PackageManager.Property getProperty(String propertyName, String packageName, String className); ParceledListSlice queryProperty(String propertyName, int componentType); + + void setKeepUninstalledPackages(in List<String> packageList); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b95b991b095c..6cfcce3f7661 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -47,8 +47,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.dex.ArtManager; +import android.content.pm.verify.domain.DomainVerificationManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -9282,4 +9282,20 @@ public abstract class PackageManager { throw e.rethrowFromSystemServer(); } } + + /** + * Set a list of apps to keep around as APKs even if no user has currently installed it. + * @param packageList List of package names to keep cached. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) + @TestApi + public void setKeepUninstalledPackages(@NonNull List<String> packageList) { + try { + ActivityThread.getPackageManager().setKeepUninstalledPackages(packageList); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 0819d1743ad6..bf8d1f6ab07b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -6151,6 +6151,56 @@ public class PackageParser { } /** + * Returns whether this instance is currently signed, or has ever been signed, with a + * signing certificate from the provided {@link Set} of {@code certDigests}. + * + * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding + * of each trusted certificate with the digest characters in upper case. If this instance + * has multiple signers then all signers must be in the provided {@code Set}. If this + * instance has a signing lineage then this method will return true if any of the previous + * signers in the lineage match one of the entries in the {@code Set}. + */ + public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) { + if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) { + return false; + } + // If an app is signed by multiple signers then all of the signers must be in the Set. + if (signatures.length > 1) { + // If the Set has less elements than the number of signatures then immediately + // return false as there's no way to satisfy the requirement of all signatures being + // in the Set. + if (certDigests.size() < signatures.length) { + return false; + } + for (Signature signature : signatures) { + String signatureDigest = PackageUtils.computeSha256Digest( + signature.toByteArray()); + if (!certDigests.contains(signatureDigest)) { + return false; + } + } + return true; + } + + String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray()); + if (certDigests.contains(signatureDigest)) { + return true; + } + if (hasPastSigningCertificates()) { + // The last element in the pastSigningCertificates array is the current signer; + // since that was verified above just check all the signers in the lineage. + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + signatureDigest = PackageUtils.computeSha256Digest( + pastSigningCertificates[i].toByteArray()); + if (certDigests.contains(signatureDigest)) { + return true; + } + } + } + return false; + } + + /** * Returns the SigningDetails with a descendant (or same) signer after verifying the * descendant has the same, a superset, or a subset of the lineage of the ancestor. * diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 35f02a8d5dd2..a2e533af64a0 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -30,6 +30,7 @@ import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Set; /** * Information you can retrieve about a particular security permission @@ -278,6 +279,15 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { @SystemApi public static final int PROTECTION_FLAG_ROLE = 0x4000000; + /** + * Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value + * of {@link android.R.attr#protectionLevel}. + * + * @hide + */ + @SystemApi + public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000; + /** @hide */ @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = { PROTECTION_FLAG_PRIVILEGED, @@ -303,6 +313,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { PROTECTION_FLAG_RETAIL_DEMO, PROTECTION_FLAG_RECENTS, PROTECTION_FLAG_ROLE, + PROTECTION_FLAG_KNOWN_SIGNER, }) @Retention(RetentionPolicy.SOURCE) public @interface ProtectionFlags {} @@ -466,6 +477,15 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { */ public @Nullable CharSequence nonLocalizedDescription; + /** + * 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 + * if the app is signed by any of these certificates. + * + * @hide + */ + public @Nullable Set<String> knownCerts; + /** @hide */ public static int fixProtectionLevel(int level) { if (level == PROTECTION_SIGNATURE_OR_SYSTEM) { @@ -570,6 +590,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) { protLevel.append("|role"); } + if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) { + protLevel.append("|knownSigner"); + } return protLevel.toString(); } diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java index fb0d90490567..9a84ded99c67 100644 --- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java @@ -655,6 +655,7 @@ public class PackageInfoWithoutStateUtils { pi.protectionLevel = p.getProtectionLevel(); pi.descriptionRes = p.getDescriptionRes(); pi.flags = p.getFlags(); + pi.knownCerts = p.getKnownCerts(); if ((flags & PackageManager.GET_META_DATA) == 0) { return pi; diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java index f99a0b1dcadb..35bb33c84d56 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermission.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java @@ -26,6 +26,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; +import java.util.Set; + /** @hide */ public class ParsedPermission extends ParsedComponent { @@ -39,6 +41,8 @@ public class ParsedPermission extends ParsedComponent { boolean tree; @Nullable private ParsedPermissionGroup parsedPermissionGroup; + @Nullable + Set<String> knownCerts; @VisibleForTesting public ParsedPermission() { @@ -81,6 +85,10 @@ public class ParsedPermission extends ParsedComponent { return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE; } + public @Nullable Set<String> getKnownCerts() { + return knownCerts; + } + public int calculateFootprint() { int size = getName().length(); if (getNonLocalizedLabel() != null) { diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java index 9012b5ce2b1e..a7cecbee8aec 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java @@ -25,6 +25,7 @@ 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; @@ -32,6 +33,8 @@ 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 { @@ -90,6 +93,43 @@ public class ParsedPermissionUtils { permission.flags = sa.getInt( R.styleable.AndroidManifestPermission_permissionFlags, 0); + final int knownCertsResource = sa.getResourceId( + R.styleable.AndroidManifestPermission_knownCerts, 0); + if (knownCertsResource != 0) { + // The knownCerts attribute supports both a string array resource as well as a + // string resource for the case where the permission should only be granted to a + // single known signer. + final String resourceType = res.getResourceTypeName(knownCertsResource); + 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)); + } + } + } else { + final String knownCert = res.getString(knownCertsResource); + if (knownCert != null) { + permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US)); + } + } + if (permission.knownCerts == null) { + Slog.w(TAG, packageName + " defines a knownSigner permission but" + + " the provided knownCerts resource is null"); + } + } else { + // If the knownCerts resource ID is null check if the app specified a string + // value for the attribute representing a single trusted signer. + final String knownCert = sa.getString( + R.styleable.AndroidManifestPermission_knownCerts); + if (knownCert != null) { + permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US)); + } + } + // For now only platform runtime permissions can be restricted if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) { permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED; 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/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 08b1e245dc83..5f5697a4ed0b 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -236,7 +236,7 @@ public class BiometricManager { @RequiresPermission(TEST_BIOMETRIC) public BiometricTestSession createTestSession(int sensorId) { try { - return new BiometricTestSession(mContext, + return new BiometricTestSession(mContext, sensorId, mService.createTestSession(sensorId, mContext.getOpPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java index 2b689899af01..1c3560882f1b 100644 --- a/core/java/android/hardware/biometrics/BiometricTestSession.java +++ b/core/java/android/hardware/biometrics/BiometricTestSession.java @@ -25,6 +25,7 @@ import android.content.Context; import android.hardware.fingerprint.FingerprintManager; import android.os.RemoteException; import android.util.ArraySet; +import android.util.Log; /** * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and @@ -33,7 +34,10 @@ import android.util.ArraySet; */ @TestApi public class BiometricTestSession implements AutoCloseable { + private static final String TAG = "BiometricTestSession"; + private final Context mContext; + private final int mSensorId; private final ITestSession mTestSession; // Keep track of users that were tested, which need to be cleaned up when finishing. @@ -42,8 +46,10 @@ public class BiometricTestSession implements AutoCloseable { /** * @hide */ - public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) { + public BiometricTestSession(@NonNull Context context, int sensorId, + @NonNull ITestSession testSession) { mContext = context; + mSensorId = sensorId; mTestSession = testSession; mTestedUsers = new ArraySet<>(); setTestHalEnabled(true); @@ -61,6 +67,7 @@ public class BiometricTestSession implements AutoCloseable { @RequiresPermission(TEST_BIOMETRIC) private void setTestHalEnabled(boolean enabled) { try { + Log.w(TAG, "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled); mTestSession.setTestHalEnabled(enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); 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/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 188a2a47fca0..a614ebfe1793 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -153,7 +153,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(TEST_BIOMETRIC) public BiometricTestSession createTestSession(int sensorId) { try { - return new BiometricTestSession(mContext, + return new BiometricTestSession(mContext, sensorId, mService.createTestSession(sensorId, mContext.getOpPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java index f4d8a65d54c6..a4817ae27fa5 100644 --- a/core/java/android/hardware/input/InputDeviceVibrator.java +++ b/core/java/android/hardware/input/InputDeviceVibrator.java @@ -74,6 +74,11 @@ final class InputDeviceVibrator extends Vibrator { } @Override + public int getId() { + return mVibratorId; + } + + @Override public boolean hasVibrator() { return true; } diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java index a381b02ab2a6..d843407c289d 100644 --- a/core/java/android/hardware/input/InputDeviceVibratorManager.java +++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java @@ -16,9 +16,12 @@ package android.hardware.input; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Binder; import android.os.CombinedVibrationEffect; import android.os.NullVibrator; +import android.os.VibrationAttributes; import android.os.Vibrator; import android.os.VibratorManager; import android.util.SparseArray; @@ -86,6 +89,7 @@ public class InputDeviceVibratorManager extends VibratorManager } } + @NonNull @Override public int[] getVibratorIds() { synchronized (mVibrators) { @@ -97,6 +101,7 @@ public class InputDeviceVibratorManager extends VibratorManager } } + @NonNull @Override public Vibrator getVibrator(int vibratorId) { synchronized (mVibrators) { @@ -107,6 +112,7 @@ public class InputDeviceVibratorManager extends VibratorManager return NullVibrator.getInstance(); } + @NonNull @Override public Vibrator getDefaultVibrator() { // Returns vibrator ID 0 @@ -119,7 +125,13 @@ public class InputDeviceVibratorManager extends VibratorManager } @Override - public void vibrate(CombinedVibrationEffect effect) { + public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect, + String reason, @Nullable VibrationAttributes attributes) { mInputManager.vibrate(mDeviceId, effect, mToken); } + + @Override + public void cancel() { + mInputManager.cancelVibrate(mDeviceId, mToken); + } } 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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index cc86a604c194..d631f689b89b 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,12 @@ 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++) { + pw.print(" custom[" + idx + "]="); + printmAh(pw, bs.customMeasuredPowerMah[idx]); + } + } pw.print(" )"); } diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java index cb4e9cba0977..c8e682c86ea7 100644 --- a/core/java/android/os/CombinedVibrationEffect.java +++ b/core/java/android/os/CombinedVibrationEffect.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.NonNull; +import android.annotation.TestApi; import android.util.SparseArray; import com.android.internal.util.Preconditions; @@ -86,7 +87,20 @@ public abstract class CombinedVibrationEffect implements Parcelable { return 0; } - /** @hide */ + /** + * Gets the estimated duration of the combined vibration in milliseconds. + * + * <p>For synced combinations this means the maximum duration of any individual {@link + * VibrationEffect}. For sequential combinations, this is a sum of each step and delays. + * + * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative + * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g. + * Prebaked effects where the length is device and potentially run-time dependent), this returns + * -1. + * + * @hide + */ + @TestApi public abstract long getDuration(); /** @hide */ @@ -256,6 +270,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { * * @hide */ + @TestApi public static final class Mono extends CombinedVibrationEffect { private final VibrationEffect mEffect; @@ -267,6 +282,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { mEffect = effect; } + @NonNull public VibrationEffect getEffect() { return mEffect; } @@ -282,6 +298,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { mEffect.validate(); } + /** @hide */ @Override public boolean hasVibrator(int vibratorId) { return true; @@ -307,7 +324,12 @@ public abstract class CombinedVibrationEffect implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_MONO); mEffect.writeToParcel(out, flags); } @@ -335,6 +357,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { * * @hide */ + @TestApi public static final class Stereo extends CombinedVibrationEffect { /** Mapping vibrator ids to effects. */ @@ -357,6 +380,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { } /** Effects to be performed in sync, where each key represents the vibrator id. */ + @NonNull public SparseArray<VibrationEffect> getEffects() { return mEffects; } @@ -394,6 +418,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { } } + /** @hide */ @Override public boolean hasVibrator(int vibratorId) { return mEffects.indexOfKey(vibratorId) >= 0; @@ -418,7 +443,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { @Override public int hashCode() { - return Objects.hash(mEffects); + return mEffects.contentHashCode(); } @Override @@ -427,7 +452,12 @@ public abstract class CombinedVibrationEffect implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_STEREO); out.writeInt(mEffects.size()); for (int i = 0; i < mEffects.size(); i++) { @@ -459,6 +489,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { * * @hide */ + @TestApi public static final class Sequential extends CombinedVibrationEffect { private final List<CombinedVibrationEffect> mEffects; private final List<Integer> mDelays; @@ -480,11 +511,13 @@ public abstract class CombinedVibrationEffect implements Parcelable { } /** Effects to be performed in sequence. */ + @NonNull public List<CombinedVibrationEffect> getEffects() { return mEffects; } /** Delay to be applied before each effect in {@link #getEffects()}. */ + @NonNull public List<Integer> getDelays() { return mDelays; } @@ -542,6 +575,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { } } + /** @hide */ @Override public boolean hasVibrator(int vibratorId) { final int effectCount = mEffects.size(); @@ -564,7 +598,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { @Override public int hashCode() { - return Objects.hash(mEffects); + return Objects.hash(mEffects, mDelays); } @Override @@ -573,7 +607,12 @@ public abstract class CombinedVibrationEffect implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_SEQUENTIAL); out.writeInt(mEffects.size()); for (int i = 0; i < mEffects.size(); i++) { diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 6a76da2cc13d..77297efa86af 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 diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 62951246a43b..124c0b00b2b2 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -1359,8 +1359,17 @@ public class Environment { } final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); - return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, - uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED; + final String opPackageName = context.getOpPackageName(); + + if (appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, uid, + opPackageName) == AppOpsManager.MODE_ALLOWED) { + return true; + } + + // Legacy external storage access is granted to instrumentations invoked with + // "--no-isolated-storage" flag. + return appOps.noteOpNoThrow(AppOpsManager.OP_NO_ISOLATED_STORAGE, uid, + opPackageName) == AppOpsManager.MODE_ALLOWED; } private static boolean isScopedStorageEnforced(boolean defaultScopedStorage, diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl deleted file mode 100644 index 1cd48dcf797b..000000000000 --- a/core/java/android/os/IVibratorService.aidl +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2007, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -import android.os.VibrationEffect; -import android.os.VibrationAttributes; -import android.os.VibratorInfo; -import android.os.IVibratorStateListener; - -/** {@hide} */ -interface IVibratorService -{ - boolean hasVibrator(); - boolean isVibrating(); - VibratorInfo getVibratorInfo(); - boolean registerVibratorStateListener(in IVibratorStateListener listener); - boolean unregisterVibratorStateListener(in IVibratorStateListener listener); - boolean hasAmplitudeControl(); - void vibrate(int uid, String opPkg, in VibrationEffect effect, - in VibrationAttributes attributes, String reason, IBinder token); - void cancelVibrate(IBinder token); -} - diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 2559a33c1ab2..dac1edea7d3e 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -4,7 +4,6 @@ per-file ExternalVibration.java = michaelwr@google.com per-file IExternalVibrationController.aidl = michaelwr@google.com per-file IExternalVibratorService.aidl = michaelwr@google.com per-file IVibratorManagerService.aidl = michaelwr@google.com -per-file IVibratorService.aidl = michaelwr@google.com per-file NullVibrator.java = michaelwr@google.com per-file SystemVibrator.java = michaelwr@google.com per-file VibrationEffect.aidl = michaelwr@google.com diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 30afe38be397..b42a495ece56 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -17,19 +17,18 @@ package android.os; import android.annotation.CallbackExecutor; -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.AudioAttributes; import android.util.ArrayMap; import android.util.Log; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.Executor; @@ -41,234 +40,178 @@ import java.util.concurrent.Executor; public class SystemVibrator extends Vibrator { private static final String TAG = "Vibrator"; - private static final int VIBRATOR_PRESENT_UNKNOWN = 0; - private static final int VIBRATOR_PRESENT_YES = 1; - private static final int VIBRATOR_PRESENT_NO = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO}) - private @interface VibratorPresent {} - - private final IVibratorService mService; - private final IVibratorManagerService mManagerService; - private final Object mLock = new Object(); - private final Binder mToken = new Binder(); + private final VibratorManager mVibratorManager; private final Context mContext; - @GuardedBy("mLock") - private VibratorInfo mVibratorInfo; - @GuardedBy("mLock") - @VibratorPresent - private int mVibratorPresent; - @GuardedBy("mDelegates") - private final ArrayMap<OnVibratorStateChangedListener, - OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>(); + @GuardedBy("mBrokenListeners") + private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>(); - @UnsupportedAppUsage - public SystemVibrator() { - mContext = null; - mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator")); - mManagerService = IVibratorManagerService.Stub.asInterface( - ServiceManager.getService("vibrator_manager")); - } + @GuardedBy("mRegisteredListeners") + private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener> + mRegisteredListeners = new ArrayMap<>(); @UnsupportedAppUsage public SystemVibrator(Context context) { super(context); mContext = context; - mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator")); - mManagerService = IVibratorManagerService.Stub.asInterface( - ServiceManager.getService("vibrator_manager")); + mVibratorManager = mContext.getSystemService(VibratorManager.class); } @Override public boolean hasVibrator() { - try { - synchronized (mLock) { - if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) { - mVibratorPresent = - mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO; - } - return mVibratorPresent == VIBRATOR_PRESENT_YES; - } - } catch (RemoteException e) { - Log.w(TAG, "Failed to query vibrator presence", e); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager."); return false; } + return mVibratorManager.getVibratorIds().length > 0; } - /** - * Check whether the vibrator is vibrating. - * - * @return True if the hardware is vibrating, otherwise false. - */ @Override public boolean isVibrating() { - if (mService == null) { - Log.w(TAG, "Failed to vibrate; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to vibrate; no vibrator manager."); return false; } - try { - return mService.isVibrating(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + for (int vibratorId : mVibratorManager.getVibratorIds()) { + if (mVibratorManager.getVibrator(vibratorId).isVibrating()) { + return true; + } } return false; } - private class OnVibratorStateChangedListenerDelegate extends - IVibratorStateListener.Stub { - private final Executor mExecutor; - private final OnVibratorStateChangedListener mListener; - - OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener, - @NonNull Executor executor) { - mExecutor = executor; - mListener = listener; - } - - @Override - public void onVibrating(boolean isVibrating) { - mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating)); + @Override + public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + if (mContext == null) { + Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); + return; } + addVibratorStateListener(mContext.getMainExecutor(), listener); } - /** - * Adds a listener for vibrator state change. If the listener was previously added and not - * removed, this call will be ignored. - * - * @param listener Listener to be added. - * @param executor The {@link Executor} on which the listener's callbacks will be executed on. - */ @Override public void addVibratorStateListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); Objects.requireNonNull(executor); - if (mService == null) { - Log.w(TAG, "Failed to add vibrate state listener; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager."); return; } - - synchronized (mDelegates) { - // If listener is already registered, reject and return. - if (mDelegates.containsKey(listener)) { - Log.w(TAG, "Listener already registered."); - return; - } - try { - final OnVibratorStateChangedListenerDelegate delegate = - new OnVibratorStateChangedListenerDelegate(listener, executor); - if (!mService.registerVibratorStateListener(delegate)) { - Log.w(TAG, "Failed to register vibrate state listener"); + AllVibratorsStateListener delegate = null; + try { + synchronized (mRegisteredListeners) { + // If listener is already registered, reject and return. + if (mRegisteredListeners.containsKey(listener)) { + Log.w(TAG, "Listener already registered."); return; } - mDelegates.put(listener, delegate); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + delegate = new AllVibratorsStateListener(executor, listener); + delegate.register(mVibratorManager); + mRegisteredListeners.put(listener, delegate); + delegate = null; } + } finally { + if (delegate != null && delegate.hasRegisteredListeners()) { + // The delegate listener was left in a partial state with listeners registered to + // some but not all vibrators. Keep track of this to try to unregister them later. + synchronized (mBrokenListeners) { + mBrokenListeners.add(delegate); + } + } + tryUnregisterBrokenListeners(); } } - /** - * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread. - * If the listener was previously added and not removed, this call will be ignored. - * - * @param listener listener to be added - */ - @Override - public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { - Objects.requireNonNull(listener); - if (mContext == null) { - Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); - return; - } - addVibratorStateListener(mContext.getMainExecutor(), listener); - } - - /** - * Removes the listener for vibrator state changes. If the listener was not previously - * registered, this call will do nothing. - * - * @param listener Listener to be removed. - */ @Override public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); - if (mService == null) { - Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager."); return; } - synchronized (mDelegates) { - // Check if the listener is registered, otherwise will return. - if (mDelegates.containsKey(listener)) { - final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener); - try { - if (!mService.unregisterVibratorStateListener(delegate)) { - Log.w(TAG, "Failed to unregister vibrate state listener"); - return; - } - mDelegates.remove(listener); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + synchronized (mRegisteredListeners) { + if (mRegisteredListeners.containsKey(listener)) { + AllVibratorsStateListener delegate = mRegisteredListeners.get(listener); + delegate.unregister(mVibratorManager); + mRegisteredListeners.remove(listener); } } + tryUnregisterBrokenListeners(); } @Override public boolean hasAmplitudeControl() { - if (mService == null) { - Log.w(TAG, "Failed to check amplitude control; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager."); return false; } - try { - return mService.hasAmplitudeControl(); - } catch (RemoteException e) { + int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + return false; } - return false; + for (int vibratorId : vibratorIds) { + if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) { + return false; + } + } + return true; } @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, AudioAttributes attributes) { - if (mManagerService == null) { - Log.w(TAG, "Failed to set always-on effect; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to set always-on effect; no vibrator manager."); return false; } - try { - VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build(); - CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); - return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr); - } catch (RemoteException e) { - Log.w(TAG, "Failed to set always-on effect.", e); - } - return false; + VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build(); + CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); + return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr); } @Override public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect, String reason, @NonNull VibrationAttributes attributes) { - if (mService == null) { - Log.w(TAG, "Failed to vibrate; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to vibrate; no vibrator manager."); return; } - try { - mService.vibrate(uid, opPkg, effect, attributes, reason, mToken); - } catch (RemoteException e) { - Log.w(TAG, "Failed to vibrate.", e); - } + CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); + mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes); } @Override public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) { - VibratorInfo vibratorInfo = getVibratorInfo(); int[] supported = new int[effectIds.length]; - for (int i = 0; i < effectIds.length; i++) { - supported[i] = vibratorInfo == null - ? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN - : vibratorInfo.isEffectSupported(effectIds[i]); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check supported effects; no vibrator manager."); + Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO); + return supported; + } + int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO); + return supported; + } + int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length]; + for (int i = 0; i < vibratorIds.length; i++) { + vibratorSupportMap[i] = mVibratorManager.getVibrator( + vibratorIds[i]).areEffectsSupported(effectIds); + } + Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES); + for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) { + for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) { + int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx]; + if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) { + supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO; + break; + } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) { + supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN; + } + } } return supported; } @@ -276,42 +219,169 @@ public class SystemVibrator extends Vibrator { @Override public boolean[] arePrimitivesSupported( @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { - VibratorInfo vibratorInfo = getVibratorInfo(); boolean[] supported = new boolean[primitiveIds.length]; - for (int i = 0; i < primitiveIds.length; i++) { - supported[i] = vibratorInfo == null - ? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check supported primitives; no vibrator manager."); + Arrays.fill(supported, false); + return supported; + } + int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + Arrays.fill(supported, false); + return supported; + } + boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length]; + for (int i = 0; i < vibratorIds.length; i++) { + vibratorSupportMap[i] = mVibratorManager.getVibrator( + vibratorIds[i]).arePrimitivesSupported(primitiveIds); + } + Arrays.fill(supported, true); + for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) { + for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) { + if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) { + supported[primitiveIdx] = false; + break; + } + } } return supported; } @Override public void cancel() { - if (mService == null) { + if (mVibratorManager == null) { + Log.w(TAG, "Failed to cancel vibrate; no vibrator manager."); return; } - try { - mService.cancelVibrate(mToken); - } catch (RemoteException e) { - Log.w(TAG, "Failed to cancel vibration.", e); + mVibratorManager.cancel(); + } + + /** + * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener} + * that were left registered to vibrators after failures to register them to all vibrators. + * + * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator + * and also fails to unregister any previously registered single listeners to other vibrators. + * + * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will + * fail silently and attempt to unregister the same broken listener later. + */ + private void tryUnregisterBrokenListeners() { + synchronized (mBrokenListeners) { + try { + for (int i = mBrokenListeners.size(); --i >= 0; ) { + mBrokenListeners.get(i).unregister(mVibratorManager); + mBrokenListeners.remove(i); + } + } catch (RuntimeException e) { + Log.w(TAG, "Failed to unregister broken listener", e); + } } } - @Nullable - private VibratorInfo getVibratorInfo() { - try { + /** Listener for a single vibrator state change. */ + private static class SingleVibratorStateListener implements OnVibratorStateChangedListener { + private final AllVibratorsStateListener mAllVibratorsListener; + private final int mVibratorIdx; + + SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) { + mAllVibratorsListener = listener; + mVibratorIdx = vibratorIdx; + } + + @Override + public void onVibratorStateChanged(boolean isVibrating) { + mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating); + } + } + + /** Listener for all vibrators state change. */ + private static class AllVibratorsStateListener { + private final Object mLock = new Object(); + private final Executor mExecutor; + private final OnVibratorStateChangedListener mDelegate; + + @GuardedBy("mLock") + private final SparseArray<SingleVibratorStateListener> mVibratorListeners = + new SparseArray<>(); + + @GuardedBy("mLock") + private int mInitializedMask; + @GuardedBy("mLock") + private int mVibratingMask; + + AllVibratorsStateListener(@NonNull Executor executor, + @NonNull OnVibratorStateChangedListener listener) { + mExecutor = executor; + mDelegate = listener; + } + + boolean hasRegisteredListeners() { + synchronized (mLock) { + return mVibratorListeners.size() > 0; + } + } + + void register(VibratorManager vibratorManager) { + int[] vibratorIds = vibratorManager.getVibratorIds(); synchronized (mLock) { - if (mVibratorInfo != null) { - return mVibratorInfo; + for (int i = 0; i < vibratorIds.length; i++) { + int vibratorId = vibratorIds[i]; + SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i); + try { + vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor, + listener); + mVibratorListeners.put(vibratorId, listener); + } catch (RuntimeException e) { + try { + unregister(vibratorManager); + } catch (RuntimeException e1) { + Log.w(TAG, + "Failed to unregister listener while recovering from a failed " + + "register call", e1); + } + throw e; + } } - if (mService == null) { - return null; + } + } + + void unregister(VibratorManager vibratorManager) { + synchronized (mLock) { + for (int i = mVibratorListeners.size(); --i >= 0; ) { + int vibratorId = mVibratorListeners.keyAt(i); + SingleVibratorStateListener listener = mVibratorListeners.valueAt(i); + vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener); + mVibratorListeners.removeAt(i); } - return mVibratorInfo = mService.getVibratorInfo(); } - } catch (RemoteException e) { - Log.w(TAG, "Failed to query vibrator info"); - throw e.rethrowFromSystemServer(); + } + + void onVibrating(int vibratorIdx, boolean vibrating) { + mExecutor.execute(() -> { + boolean anyVibrating; + synchronized (mLock) { + int allInitializedMask = 1 << mVibratorListeners.size() - 1; + int vibratorMask = 1 << vibratorIdx; + if ((mInitializedMask & vibratorMask) == 0) { + // First state report for this vibrator, set vibrating initial value. + mInitializedMask |= vibratorMask; + mVibratingMask |= vibrating ? vibratorMask : 0; + } else { + // Flip vibrating value, if changed. + boolean prevVibrating = (mVibratingMask & vibratorMask) != 0; + if (prevVibrating != vibrating) { + mVibratingMask ^= vibratorMask; + } + } + if (mInitializedMask != allInitializedMask) { + // Wait for all vibrators initial state to be reported before delegating. + return; + } + anyVibrating = mVibratingMask != 0; + } + mDelegate.onVibratorStateChanged(anyVibrating); + }); } } } diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java new file mode 100644 index 000000000000..b528eb157e36 --- /dev/null +++ b/core/java/android/os/SystemVibratorManager.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.AudioAttributes; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * VibratorManager implementation that controls the system vibrators. + * + * @hide + */ +public class SystemVibratorManager extends VibratorManager { + private static final String TAG = "VibratorManager"; + + private final IVibratorManagerService mService; + private final Context mContext; + private final Binder mToken = new Binder(); + private final Object mLock = new Object(); + @GuardedBy("mLock") + private int[] mVibratorIds; + @GuardedBy("mLock") + private final SparseArray<Vibrator> mVibrators = new SparseArray<>(); + + @GuardedBy("mLock") + private final ArrayMap<Vibrator.OnVibratorStateChangedListener, + OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>(); + + /** + * @hide to prevent subclassing from outside of the framework + */ + public SystemVibratorManager(Context context) { + super(context); + mContext = context; + mService = IVibratorManagerService.Stub.asInterface( + ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE)); + } + + @NonNull + @Override + public int[] getVibratorIds() { + synchronized (mLock) { + if (mVibratorIds != null) { + return mVibratorIds; + } + try { + if (mService == null) { + Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service."); + } else { + return mVibratorIds = mService.getVibratorIds(); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return new int[0]; + } + } + + @NonNull + @Override + public Vibrator getVibrator(int vibratorId) { + synchronized (mLock) { + Vibrator vibrator = mVibrators.get(vibratorId); + if (vibrator != null) { + return vibrator; + } + VibratorInfo info = null; + try { + if (mService == null) { + Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service."); + } else { + info = mService.getVibratorInfo(vibratorId); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + if (info != null) { + vibrator = new SingleVibrator(info); + mVibrators.put(vibratorId, vibrator); + } else { + vibrator = NullVibrator.getInstance(); + } + return vibrator; + } + } + + @NonNull + @Override + public Vibrator getDefaultVibrator() { + return mContext.getSystemService(Vibrator.class); + } + + @Override + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to set always-on effect; no vibrator manager service."); + return false; + } + try { + return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes); + } catch (RemoteException e) { + Log.w(TAG, "Failed to set always-on effect.", e); + } + return false; + } + + @Override + public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect, + String reason, @Nullable VibrationAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to vibrate; no vibrator manager service."); + return; + } + try { + mService.vibrate(uid, opPkg, effect, attributes, reason, mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to vibrate.", e); + } + } + + @Override + public void cancel() { + if (mService == null) { + Log.w(TAG, "Failed to cancel vibration; no vibrator manager service."); + return; + } + try { + mService.cancelVibrate(mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to cancel vibration.", e); + } + } + + /** Listener for vibrations on a single vibrator. */ + private static class OnVibratorStateChangedListenerDelegate extends + IVibratorStateListener.Stub { + private final Executor mExecutor; + private final Vibrator.OnVibratorStateChangedListener mListener; + + OnVibratorStateChangedListenerDelegate( + @NonNull Vibrator.OnVibratorStateChangedListener listener, + @NonNull Executor executor) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onVibrating(boolean isVibrating) { + mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating)); + } + } + + /** Controls vibrations on a single vibrator. */ + private final class SingleVibrator extends Vibrator { + private final VibratorInfo mVibratorInfo; + + SingleVibrator(@NonNull VibratorInfo vibratorInfo) { + mVibratorInfo = vibratorInfo; + } + + @Override + public int getId() { + return mVibratorInfo.getId(); + } + + @Override + public boolean hasVibrator() { + return true; + } + + @Override + public boolean hasAmplitudeControl() { + return mVibratorInfo.hasAmplitudeControl(); + } + + @NonNull + @Override + public int[] areEffectsSupported(@NonNull int... effectIds) { + int[] supported = new int[effectIds.length]; + for (int i = 0; i < effectIds.length; i++) { + supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]); + } + return supported; + } + + @Override + public boolean[] arePrimitivesSupported( + @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + boolean[] supported = new boolean[primitiveIds.length]; + for (int i = 0; i < primitiveIds.length; i++) { + supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]); + } + return supported; + } + + @Override + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId() + + "; no vibrator manager service."); + return false; + } + try { + VibrationAttributes attr = new VibrationAttributes.Builder( + attributes, effect).build(); + CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced() + .addVibrator(mVibratorInfo.getId(), effect) + .combine(); + return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr); + } catch (RemoteException e) { + Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()); + } + return false; + } + + @Override + public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason, + @NonNull VibrationAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId() + + "; no vibrator manager service."); + return; + } + try { + CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced() + .addVibrator(mVibratorInfo.getId(), vibe) + .combine(); + mService.vibrate(uid, opPkg, combined, attributes, reason, mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to vibrate.", e); + } + } + + @Override + public void cancel() { + if (mService == null) { + Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId() + + "; no vibrator manager service."); + return; + } + try { + mService.cancelVibrate(mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e); + } + } + + @Override + public boolean isVibrating() { + if (mService == null) { + Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId() + + "; no vibrator service."); + return false; + } + try { + return mService.isVibrating(mVibratorInfo.getId()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + @Override + public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + if (mContext == null) { + Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); + return; + } + addVibratorStateListener(mContext.getMainExecutor(), listener); + } + + @Override + public void addVibratorStateListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + Objects.requireNonNull(executor); + if (mService == null) { + Log.w(TAG, + "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId() + + "; no vibrator service."); + return; + } + synchronized (mLock) { + // If listener is already registered, reject and return. + if (mListeners.containsKey(listener)) { + Log.w(TAG, "Listener already registered."); + return; + } + try { + OnVibratorStateChangedListenerDelegate delegate = + new OnVibratorStateChangedListenerDelegate(listener, executor); + if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) { + Log.w(TAG, "Failed to add vibrate state listener to vibrator " + + mVibratorInfo.getId()); + return; + } + mListeners.put(listener, delegate); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + @Override + public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + if (mService == null) { + Log.w(TAG, "Failed to remove vibrate state listener from vibrator " + + mVibratorInfo.getId() + "; no vibrator service."); + return; + } + synchronized (mLock) { + // Check if the listener is registered, otherwise will return. + if (mListeners.containsKey(listener)) { + OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener); + try { + if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(), + delegate)) { + Log.w(TAG, "Failed to remove vibrate state listener from vibrator " + + mVibratorInfo.getId()); + return; + } + mListeners.remove(listener); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + } + } +} diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 7d85d13094a1..d6fa733927fb 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -184,6 +184,15 @@ public abstract class Vibrator { } /** + * Return the ID of this vibrator. + * + * @return The id of the vibrator controlled by this service. + */ + public int getId() { + return -1; + } + + /** * Check whether the hardware has a vibrator. * * @return True if the hardware has a vibrator, else false. diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java index 1d5a58745279..5dd38b6cbd86 100644 --- a/core/java/android/os/VibratorManager.java +++ b/core/java/android/os/VibratorManager.java @@ -17,45 +17,123 @@ package android.os; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.app.ActivityThread; +import android.content.Context; +import android.util.Log; /** - * VibratorManager provides access to multiple vibrators, as well as the ability to run them in - * a synchronized fashion. + * Class that provides access to all vibrators from the device, as well as the ability to run them + * in a synchronized fashion. + * <p> + * If your process exits, any vibration you started will stop. + * </p> */ +@SystemService(Context.VIBRATOR_MANAGER_SERVICE) public abstract class VibratorManager { - /** @hide */ - protected static final String TAG = "VibratorManager"; + private static final String TAG = "VibratorManager"; + + private final String mPackageName; /** - * {@hide} + * @hide to prevent subclassing from outside of the framework */ public VibratorManager() { + mPackageName = ActivityThread.currentPackageName(); + } + + /** + * @hide to prevent subclassing from outside of the framework + */ + protected VibratorManager(Context context) { + mPackageName = context.getOpPackageName(); } /** - * This method lists all available actuator ids, returning a possible empty list. - * If the device has only a single actuator, this should return a single entry with a - * default id. + * List all available vibrator ids, returning a possible empty list. + * + * @return An array containing the ids of the vibrators available on the device. */ @NonNull public abstract int[] getVibratorIds(); /** - * Returns a Vibrator service for given id. - * This allows users to perform a vibration effect on a single actuator. - */ + * Retrieve a single vibrator by id. + * + * @param vibratorId The id of the vibrator to be retrieved. + * @return The vibrator with given {@code vibratorId}, never null. + */ @NonNull public abstract Vibrator getVibrator(int vibratorId); /** - * Returns the system default Vibrator service. - */ + * Returns the system default Vibrator service. + */ @NonNull public abstract Vibrator getDefaultVibrator(); /** - * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect - * to the respective actuator, in sync. + * Configure an always-on haptics effect. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON) + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) { + Log.w(TAG, "Always-on effects aren't supported"); + return false; + } + + /** + * Vibrate with a given combination of effects. + * + * <p> + * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link + * VibrationEffect} to be played on one or more vibrators. + * </p> + * + * @param effect an array of longs of times for which to turn the vibrator on or off. + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public final void vibrate(@NonNull CombinedVibrationEffect effect) { + vibrate(effect, null); + } + + /** + * Vibrate with a given combination of effects. + * + * <p> + * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link + * VibrationEffect} to be played on one or more vibrators. + * </p> + * + * @param effect an array of longs of times for which to turn the vibrator on or off. + * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example, + * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or + * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with + * incoming calls. + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public final void vibrate(@NonNull CombinedVibrationEffect effect, + @Nullable VibrationAttributes attributes) { + vibrate(Process.myUid(), mPackageName, effect, null, attributes); + } + + /** + * Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the + * caller to specify the vibration is owned by someone else and set reason for vibration. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect, + String reason, @Nullable VibrationAttributes attributes); + + /** + * Turn all the vibrators off. */ - public abstract void vibrate(@NonNull CombinedVibrationEffect effect); + @RequiresPermission(android.Manifest.permission.VIBRATE) + public abstract void cancel(); } 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..053fb2b1b4eb 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,7 @@ import android.util.ArraySet; */ public class NotificationListenerFilter implements Parcelable { private int mAllowedNotificationTypes; - private ArraySet<String> mDisallowedPackages; + private ArraySet<VersionedPackage> mDisallowedPackages; public NotificationListenerFilter() { mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS @@ -41,7 +42,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 +52,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 +79,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 +87,7 @@ public class NotificationListenerFilter implements Parcelable { return mAllowedNotificationTypes; } - public ArraySet<String> getDisallowedPackages() { + public ArraySet<VersionedPackage> getDisallowedPackages() { return mDisallowedPackages; } @@ -93,10 +95,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/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java index cbc0479a4c07..20f05981e962 100644 --- a/core/java/android/view/SurfaceSession.java +++ b/core/java/android/view/SurfaceSession.java @@ -32,7 +32,6 @@ public final class SurfaceSession { private static native long nativeCreate(); private static native void nativeDestroy(long ptr); - private static native void nativeKill(long ptr); /** Create a new connection with the surface flinger. */ @UnsupportedAppUsage @@ -44,22 +43,22 @@ public final class SurfaceSession { @Override protected void finalize() throws Throwable { try { - if (mNativeClient != 0) { - nativeDestroy(mNativeClient); - } + kill(); } finally { super.finalize(); } } /** - * Forcibly detach native resources associated with this object. - * Unlike destroy(), after this call any surfaces that were created - * from the session will no longer work. + * Remove the reference to the native Session object. The native object may still exist if + * there are other references to it, but it cannot be accessed from this Java object anymore. */ @UnsupportedAppUsage public void kill() { - nativeKill(mNativeClient); + if (mNativeClient != 0) { + nativeDestroy(mNativeClient); + mNativeClient = 0; + } } } 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 4716141ee8d3..6b13a290b20e 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); } @@ -1913,25 +1914,18 @@ public final class ViewRootImpl implements ViewParent, private boolean updateBoundsLayer(SurfaceControl.Transaction t) { if (mBoundsLayer != null) { setBoundsLayerCrop(t); - t.deferTransactionUntil(mBoundsLayer, getSurfaceControl(), - mSurface.getNextFrameNumber()); return true; } return false; } - private void prepareSurfaces(boolean sizeChanged) { + private void prepareSurfaces() { final SurfaceControl.Transaction t = mTransaction; final SurfaceControl sc = getSurfaceControl(); if (!sc.isValid()) return; - boolean applyTransaction = updateBoundsLayer(t); - if (sizeChanged) { - applyTransaction = true; - t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y); - } - if (applyTransaction) { - t.apply(); + if (updateBoundsLayer(t)) { + mergeWithNextTransaction(t, mSurface.getNextFrameNumber()); } } @@ -3036,7 +3030,7 @@ public final class ViewRootImpl implements ViewParent, // stopping, but on the client side it doesn't get stopped since it's restarted quick // enough. WMS doesn't want to keep around old children since they will leak when the // client creates new children. - prepareSurfaces(surfaceSizeChanged); + prepareSurfaces(); } final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); @@ -3070,8 +3064,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(); + } } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 90c8e17a6984..7b2bb73ff562 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -115,7 +115,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** @@ -1083,19 +1082,6 @@ public final class InputMethodManager { } } - private static class ImeThreadFactory implements ThreadFactory { - private final String mThreadName; - - ImeThreadFactory(String name) { - mThreadName = name; - } - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, mThreadName); - } - } - final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 61ff36c09cb9..681fd147a51f 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -18,6 +18,7 @@ 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; @@ -27,6 +28,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; 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 +113,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; @@ -150,9 +155,18 @@ public class EdgeEffect { * @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( @@ -248,6 +262,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 +282,56 @@ 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; + 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 +347,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 +378,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 +389,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); } /** @@ -447,6 +517,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 +529,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 +543,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 +559,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/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java index b484dfacbf6c..2904a8c889a2 100644 --- a/core/java/android/widget/ToastPresenter.java +++ b/core/java/android/widget/ToastPresenter.java @@ -172,6 +172,22 @@ public class ToastPresenter { } /** + * Update the LayoutParameters of the currently showing toast view. This is used for layout + * updates based on orientation changes. + */ + public void updateLayoutParams(int xOffset, int yOffset, float horizontalMargin, + float verticalMargin, int gravity) { + checkState(mView != null, "Toast must be showing to update its layout parameters."); + Configuration config = mResources.getConfiguration(); + mParams.gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection()); + mParams.x = xOffset; + mParams.y = yOffset; + mParams.horizontalMargin = horizontalMargin; + mParams.verticalMargin = verticalMargin; + addToastView(); + } + + /** * Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code * packageName} is a cross-user package. * @@ -221,18 +237,7 @@ public class ToastPresenter { adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset, horizontalMargin, verticalMargin, removeWindowAnimations); - if (mView.getParent() != null) { - mWindowManager.removeView(mView); - } - try { - mWindowManager.addView(mView, mParams); - } catch (WindowManager.BadTokenException e) { - // Since the notification manager service cancels the token right after it notifies us - // to cancel the toast there is an inherent race and we may attempt to add a window - // after the token has been invalidated. Let us hedge against that. - Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e); - return; - } + addToastView(); trySendAccessibilityEvent(mView, mPackageName); if (callback != null) { try { @@ -288,4 +293,19 @@ public class ToastPresenter { view.dispatchPopulateAccessibilityEvent(event); mAccessibilityManager.sendAccessibilityEvent(event); } + + private void addToastView() { + if (mView.getParent() != null) { + mWindowManager.removeView(mView); + } + try { + mWindowManager.addView(mView, mParams); + } catch (WindowManager.BadTokenException e) { + // Since the notification manager service cancels the token right after it notifies us + // to cancel the toast there is an inherent race and we may attempt to add a window + // after the token has been invalidated. Let us hedge against that. + Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e); + return; + } + } } 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/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index 9049ca56bc53..fae58622d91e 100644 --- a/core/java/com/android/internal/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal; +package com.android.internal.display; import android.content.ContentResolver; diff --git a/core/java/com/android/internal/display/OWNERS b/core/java/com/android/internal/display/OWNERS new file mode 100644 index 000000000000..20b75be9f11f --- /dev/null +++ b/core/java/com/android/internal/display/OWNERS @@ -0,0 +1,3 @@ +include /services/core/java/com/android/server/display/OWNERS + +flc@google.com diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java index af61f91841fa..9b88516e547e 100644 --- a/core/java/com/android/internal/os/BatterySipper.java +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -133,6 +133,7 @@ public class BatterySipper implements Comparable<BatterySipper> { public double wakeLockPowerMah; public double wifiPowerMah; public double systemServiceCpuPowerMah; + public double[] customMeasuredPowerMah; // Do not include this sipper in results because it is included // in an aggregate sipper. @@ -251,6 +252,17 @@ 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]; + } + } + } } /** @@ -264,6 +276,11 @@ 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]; + } + } 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 6e41b3f4ae06..a39d19e0d597 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -7157,20 +7157,34 @@ public class BatteryStatsImpl extends BatteryStats { @Override public long getScreenOnEnergy() { + return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON); + } + + @Override + public long getScreenDozeEnergy() { + return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE); + } + + /** + * Returns the energy in microjoules that the given standard energy bucket consumed. + * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable + * + * @param bucket standard energy bucket of interest + * @return energy (in microjoules) used for this energy bucket + */ + private long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) { if (mGlobalMeasuredEnergyStats == null) { return ENERGY_DATA_UNAVAILABLE; } - return mGlobalMeasuredEnergyStats - .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON); + return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket); } @Override - public long getScreenDozeEnergy() { + public @Nullable long[] getCustomMeasuredEnergiesMicroJoules() { if (mGlobalMeasuredEnergyStats == null) { - return ENERGY_DATA_UNAVAILABLE; + return null; } - return mGlobalMeasuredEnergyStats - .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE); + return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergies(); } @Override public long getStartClockTime() { @@ -7941,6 +7955,13 @@ public class BatteryStatsImpl extends BatteryStats { .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate); } + /** Adds the given energy to the given custom energy bucket for this uid. */ + private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket, + boolean accumulate) { + getOrCreateMeasuredEnergyStatsLocked() + .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate); + } + /** * Returns the energy used by this uid for a standard energy bucket of interest. * @param bucket standard energy bucket of interest @@ -7957,6 +7978,18 @@ public class BatteryStatsImpl extends BatteryStats { return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket); } + @Override + public long[] getCustomMeasuredEnergiesMicroJoules() { + if (mBsi.mGlobalMeasuredEnergyStats == null) { + return null; + } + if (mUidMeasuredEnergyStats == null) { + // 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.getAccumulatedCustomBucketEnergies(); + } + /** * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time * since last marked. Also sets the mark time for both these timers. @@ -12465,6 +12498,42 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * Accumulate Custom energy bucket energy, globally and for each app. + * + * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called. + * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called. + * Data inside uidEnergies will not be modified (treated immutable). + */ + public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket, + long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) { + if (DEBUG_ENERGY) { + Slog.d(TAG, "Updating attributed measured energy stats for custom bucket " + + customEnergyBucket + + " with total energy " + totalEnergyUJ + + " and uid energies " + String.valueOf(uidEnergies)); + } + if (mGlobalMeasuredEnergyStats == null) return; + if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return; + + mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true); + + if (uidEnergies == null) return; + final int numUids = uidEnergies.size(); + for (int i = 0; i < numUids; i++) { + 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? + final Uid uidObj = getUidStatsLocked(uidInt); + uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true); + } + } + + /** * Read and record Rail Energy data. */ public void updateRailStatsLocked() { @@ -12933,16 +13002,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); @@ -13001,6 +13071,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) { @@ -13047,21 +13120,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) { @@ -13077,21 +13154,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) { @@ -14454,7 +14535,12 @@ public class BatteryStatsImpl extends BatteryStats { */ @GuardedBy("this") public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) { - if (mGlobalMeasuredEnergyStats == null) return; + pw.printf("On battery measured energy stats (microjoules) \n"); + if (mGlobalMeasuredEnergyStats == null) { + pw.printf(" Not supported on this device.\n"); + return; + } + dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats); int size = mUidStats.size(); @@ -14472,7 +14558,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..2798b72ab21f 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -72,6 +72,7 @@ public class BatteryUsageStatsProvider { 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/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/power/MeasuredEnergyArray.java b/core/java/com/android/internal/power/MeasuredEnergyArray.java deleted file mode 100644 index 1f6dc260a197..000000000000 --- a/core/java/com/android/internal/power/MeasuredEnergyArray.java +++ /dev/null @@ -1,66 +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.internal.power; - - -import android.annotation.IntDef; - -import com.android.internal.os.RailStats; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Interface to provide subsystem energy data. - * TODO: replace this and {@link RailStats} once b/173077356 is done - */ -public interface MeasuredEnergyArray { - int SUBSYSTEM_UNKNOWN = -1; - int SUBSYSTEM_DISPLAY = 0; - int NUMBER_SUBSYSTEMS = 1; - String[] SUBSYSTEM_NAMES = {"display"}; - - - @IntDef(prefix = { "SUBSYSTEM_" }, value = { - SUBSYSTEM_UNKNOWN, - SUBSYSTEM_DISPLAY, - }) - @Retention(RetentionPolicy.SOURCE) - @interface MeasuredEnergySubsystem {} - - /** - * Get the subsystem at an index in array. - * - * @param index into the array. - * @return subsystem. - */ - @MeasuredEnergySubsystem - int getSubsystem(int index); - - /** - * Get the energy (in microjoules) consumed since boot of the subsystem at an index. - * - * @param index into the array. - * @return energy (in microjoules) consumed since boot. - */ - long getEnergy(int index); - - /** - * Return number of subsystems in the array. - */ - int size(); -} diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index e310f8d9b36e..d7b4d78c56cf 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -28,7 +28,6 @@ import android.util.Slog; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -239,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; @@ -247,7 +247,18 @@ public class MeasuredEnergyStats { } /** - * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}. + * 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) { if (Display.isOnState(screenState)) { @@ -405,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)); @@ -432,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,7 +465,9 @@ public class MeasuredEnergyStats { return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS; } - private boolean isValidCustomBucket(int customBucket) { + /** 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/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 488b18db66d6..c6a040c162ab 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -47,6 +47,13 @@ public class CollectionUtils { private CollectionUtils() { /* cannot be instantiated */ } /** + * @see Collection#contains(Object) + */ + public static <T> boolean contains(@Nullable Collection<T> collection, T element) { + return collection != null && collection.contains(element); + } + + /** * Returns a list of items from the provided list that match the given condition. * * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index af06e2e80cec..c7439f1b32d4 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -354,7 +354,7 @@ static void NativeDestroy(void* ptr) { } static jlong NativeGetFinalizer(JNIEnv* /*env*/, jclass /*clazz*/) { - return reinterpret_cast<jlong>(&NativeDestroy); + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&NativeDestroy)); } static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) { diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index c64174b93c0d..22abe6f47893 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -540,6 +540,15 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0); if (outMemtrackArray != NULL) { outMemtrackArray[0] = memtrack; + if (env->GetArrayLength(outMemtrack) >= 2) { + outMemtrackArray[1] = graphics_mem.graphics; + } + if (env->GetArrayLength(outMemtrack) >= 3) { + outMemtrackArray[2] = graphics_mem.gl; + } + if (env->GetArrayLength(outMemtrack) >= 4) { + outMemtrackArray[3] = graphics_mem.other; + } } env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0); } diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp index 191f748d80bf..0aac07d17cdc 100644 --- a/core/jni/android_view_SurfaceSession.cpp +++ b/core/jni/android_view_SurfaceSession.cpp @@ -51,19 +51,12 @@ static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { client->decStrong((void*)nativeCreate); } -static void nativeKill(JNIEnv* env, jclass clazz, jlong ptr) { - SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr); - client->dispose(); -} - static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "nativeCreate", "()J", (void*)nativeCreate }, { "nativeDestroy", "(J)V", (void*)nativeDestroy }, - { "nativeKill", "(J)V", - (void*)nativeKill } }; int register_android_view_SurfaceSession(JNIEnv* env) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index dc1cc329d460..99014c5442b0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1055,6 +1055,14 @@ android:description="@string/permdesc_accessImsCallService" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions. + Only granted if the application is a system app AND is in the Default SMS Role. + The permission is revoked when the app is taken out of the Default SMS Role. + <p>Protection level: internal|role + --> + <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" + android:protectionLevel="internal|role" /> + <!-- Allows an application to read the user's call log. <p class="note"><strong>Note:</strong> If your app uses the {@link #READ_CONTACTS} permission and <em>both</em> your <a diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index b4e580aac959..0ae6a76e2a60 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -311,6 +311,10 @@ <flag name="recents" value="0x2000000" /> <!-- Additional flag from base permission type: this permission is managed by role. --> <flag name="role" value="0x4000000" /> + <!-- Additional flag from base permission type: this permission can also be granted if the + requesting application is signed by, or has in its signing lineage, any of the + certificate digests declared in {@link android.R.attr#knownCerts}. --> + <flag name="knownSigner" value="0x8000000" /> </attr> <!-- Flags indicating more context for a permission group. --> @@ -364,6 +368,15 @@ {@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. --> <attr name="permissionGroup" format="string" /> + <!-- A reference to an array resource containing the signing certificate digests to be granted + this permission when using the {@code knownSigner} protection flag. The digest should + be computed over the DER encoding of the trusted certificate using the SHA-256 digest + algorithm. + <p> + If only a single signer is declared this can also be a string resource, or the digest + can be declared inline as the value for this attribute. --> + <attr name="knownCerts" format="reference|string" /> + <!-- Specify the name of a user ID that will be shared between multiple packages. By default, each package gets its own unique user-id. By setting this value on two or more packages, each of these packages @@ -1935,6 +1948,7 @@ <attr name="request" /> <attr name="protectionLevel" /> <attr name="permissionFlags" /> + <attr name="knownCerts" /> </declare-styleable> <!-- The <code>permission-group</code> tag declares a logical grouping of diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index beae9353a10f..09ca12aa9744 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1946,8 +1946,6 @@ <string name="config_systemAutomotiveProjection" translatable="false"></string> <!-- The name of the package that will hold the system cluster service role. --> <string name="config_systemAutomotiveCluster" translatable="false"></string> - <!-- The name of the package that will hold the system video call role. --> - <string name="config_systemVideoCall" translatable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 40c5206b86d8..068987ec8afe 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3064,6 +3064,7 @@ <public name="previewLayout" /> <public name="clipToOutline" /> <public name="edgeEffectType" /> + <public name="knownCerts" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> @@ -3118,8 +3119,6 @@ <!-- @hide @SystemApi @TestApi --> <public name="config_systemAutomotiveCluster" /> <!-- @hide @SystemApi @TestApi --> - <public name="config_systemVideoCall" /> - <!-- @hide @SystemApi @TestApi --> <public name="config_systemAutomotiveProjection" /> </public-group> diff --git a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java index 6bcec25b9368..266ff3dd09f6 100644 --- a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java +++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.timedetector; +package android.app.time; import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable; diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java index bffd1e4a86d6..49b720cfba07 100644 --- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java +++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.fail; import android.content.pm.PackageParser.SigningDetails; import android.util.ArraySet; +import android.util.PackageUtils; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -817,6 +818,124 @@ public class SigningDetailsTest { assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION)); } + @Test + public void hasAncestorOrSelfWithDigest_nullSet_returnsFalse() throws Exception { + // The hasAncestorOrSelfWithDigest method is intended to verify whether the SigningDetails + // is currently signed, or has previously been signed, by any of the certificate digests + // in the provided Set. This test verifies if a null Set is provided then false is returned. + SigningDetails details = createSigningDetails(FIRST_SIGNATURE); + + assertFalse(details.hasAncestorOrSelfWithDigest(null)); + } + + @Test + public void hasAncestorOrSelfWithDigest_unknownDetails_returnsFalse() throws Exception { + // If hasAncestorOrSelfWithDigest is invoked against an UNKNOWN + // instance of the SigningDetails then false is returned. + SigningDetails details = SigningDetails.UNKNOWN; + Set<String> digests = createDigestSet(FIRST_SIGNATURE); + + assertFalse(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_singleSignerInSet_returnsTrue() throws Exception { + // If the single signer of an app is in the provided digest Set then + // the method should return true. + SigningDetails details = createSigningDetails(FIRST_SIGNATURE); + Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE); + + assertTrue(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_singleSignerNotInSet_returnsFalse() throws Exception { + // If the single signer of an app is not in the provided digest Set then + // the method should return false. + SigningDetails details = createSigningDetails(FIRST_SIGNATURE); + Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE); + + assertFalse(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_multipleSignersInSet_returnsTrue() throws Exception { + // If an app is signed by multiple signers and all of the signers are in + // the digest Set then the method should return true. + SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE); + Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE); + + assertTrue(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_multipleSignersNotInSet_returnsFalse() + throws Exception { + // If an app is signed by multiple signers then all signers must be in the digest Set; if + // only a subset of the signers are in the Set then the method should return false. + SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE); + Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE); + + assertFalse(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_multipleSignersOneInSet_returnsFalse() + throws Exception { + // If an app is signed by multiple signers and the Set size is smaller than the number of + // signers then the method should immediately return false since there's no way for the + // requirement of all signers in the Set to be met. + SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE); + Set<String> digests = createDigestSet(FIRST_SIGNATURE); + + assertFalse(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_lineageSignerInSet_returnsTrue() throws Exception { + // If an app has a rotated signing key and a previous key in the lineage is in the digest + // Set then this method should return true. + SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE); + Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE); + + assertTrue(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_lineageSignerNotInSet_returnsFalse() throws Exception { + // If an app has a rotated signing key, but neither the current key nor any of the signers + // in the lineage are in the digest set then the method should return false. + SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE); + Set<String> digests = createDigestSet(THIRD_SIGNATURE, FOURTH_SIGNATURE); + + assertFalse(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_lastSignerInLineageInSet_returnsTrue() + throws Exception { + // If an app has multiple signers in the lineage only one of those signers must be in the + // Set for this method to return true. This test verifies if the last signer in the lineage + // is in the set then the method returns true. + SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE, + THIRD_SIGNATURE); + Set<String> digests = createDigestSet(SECOND_SIGNATURE); + + assertTrue(details.hasAncestorOrSelfWithDigest(digests)); + } + + @Test + public void hasAncestorOrSelfWithDigest_nullLineageSingleSIgner_returnsFalse() + throws Exception { + // Under some instances an app with only a single signer can have a null lineage; this + // test verifies that null lineage does not result in a NullPointerException and instead the + // method returns false if the single signer is not in the Set. + SigningDetails details = createSigningDetails(true, FIRST_SIGNATURE); + Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE); + + assertFalse(details.hasAncestorOrSelfWithDigest(digests)); + } + private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception { int[] capabilities = new int[signers.length]; for (int i = 0; i < capabilities.length; i++) { @@ -853,12 +972,21 @@ public class SigningDetailsTest { // If there are multiple signers then the pastSigningCertificates should be set to null, but // if there is only a single signer both the current signer and the past signers should be // set to that one signer. - if (signers.length > 1) { + if (signers.length > 1 || useNullPastSigners) { return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null); } return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures); } + private Set<String> createDigestSet(String... signers) { + Set<String> digests = new ArraySet<>(); + for (String signer : signers) { + String digest = PackageUtils.computeSha256Digest(new Signature(signer).toByteArray()); + digests.add(digest); + } + return digests; + } + private void assertSigningDetailsContainsLineage(SigningDetails details, String... pastSigners) { // This method should only be invoked for results that contain a single signer. diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java index 564103efef65..11239db9f404 100644 --- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java @@ -17,6 +17,8 @@ package android.os; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.testng.Assert.assertThrows; @@ -31,7 +33,6 @@ import java.util.Arrays; @Presubmit @RunWith(JUnit4.class) public class CombinedVibrationEffectTest { - private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255); private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1); @@ -172,6 +173,37 @@ public class CombinedVibrationEffectTest { } @Test + public void testHasVibratorMono_returnsTrueForAnyVibrator() { + CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); + assertTrue(effect.hasVibrator(0)); + assertTrue(effect.hasVibrator(1)); + } + + @Test + public void testHasVibratorStereo_returnsOnlyTheIdsSet() { + CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() + .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .combine(); + assertFalse(effect.hasVibrator(0)); + assertTrue(effect.hasVibrator(1)); + assertFalse(effect.hasVibrator(2)); + } + + @Test + public void testHasVibratorSequential_returnsNestedVibrators() { + CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential() + .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addNext(CombinedVibrationEffect.startSynced() + .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) + .combine()) + .combine(); + assertFalse(effect.hasVibrator(0)); + assertTrue(effect.hasVibrator(1)); + assertTrue(effect.hasVibrator(2)); + } + + @Test public void testSerializationMono() { CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT); diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS new file mode 100644 index 000000000000..581da714f326 --- /dev/null +++ b/core/tests/coretests/src/android/provider/OWNERS @@ -0,0 +1 @@ +include /core/java/android/provider/OWNERS 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 97c07eaa819b..6652c64c4344 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -24,6 +24,7 @@ import android.os.BatteryStats; import android.os.BatteryStats.HistoryItem; import android.os.BatteryStats.Uid.Sensor; import android.os.WorkSource; +import android.util.SparseLongArray; import android.view.Display; import androidx.test.filters.SmallTest; @@ -583,6 +584,95 @@ public class BatteryStatsNoteTest extends TestCase { checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi); } + @SmallTest + public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setOnBatteryInternal(true); + + final int uid1 = 11500; + final int uid2 = 11501; + + // Initially, all custom buckets report energy of 0. + checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi); + } + + @SmallTest + public void testUpdateCustomMeasuredEnergyDataLocked() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + + final int bucketA = 0; // Custom bucket 0 + final int bucketB = 1; // Custom bucket 1 + + long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids) + long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids) + + final int uid1 = 10500; + long blame1A = 0; // Blame for uid1 in bucketA + long blame1B = 0; // Blame for uid1 in bucketB + + final int uid2 = 10501; + long blame2A = 0; // Blame for uid2 in bucketA + long blame2B = 0; // Blame for uid2 in bucketB + + final SparseLongArray newEnergiesA = new SparseLongArray(2); + final SparseLongArray newEnergiesB = new SparseLongArray(2); + + + // ----- Case A: battery off (so blame does not increase) + bi.setOnBatteryInternal(false); + + newEnergiesA.put(uid1, 20_000); + // Implicit newEnergiesA.put(uid2, 0); + bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA); + + newEnergiesB.put(uid1, 60_000); + // Implicit newEnergiesB.put(uid2, 0); + bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB); + + checkCustomMeasuredEnergy( + "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi); + + + // ----- Case B: battery on + bi.setOnBatteryInternal(true); + + newEnergiesA.put(uid1, 7_000); blame1A += 7_000; + // Implicit newEnergiesA.put(uid2, 0); blame2A += 0; + bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA); + totalBlameA += 310_000; + + newEnergiesB.put(uid1, 63_000); blame1B += 63_000; + newEnergiesB.put(uid2, 15_000); blame2B += 15_000; + bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB); + totalBlameB += 790_000; + + checkCustomMeasuredEnergy( + "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi); + + + // ----- Case C: battery still on + newEnergiesA.delete(uid1); blame1A += 0; + newEnergiesA.put(uid2, 16_000); blame2A += 16_000; + bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA); + totalBlameA += 560_000; + + bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null); + totalBlameB += 10_000; + + checkCustomMeasuredEnergy( + "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi); + + + // ----- Case D: battery still on + bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA); + bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1)); + totalBlameB += 15_000; + checkCustomMeasuredEnergy( + "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi); + } + private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) { // Note that noteUidProcessStateLocked uses ActivityManager process states. if (fgOn) { @@ -610,4 +700,33 @@ public class BatteryStatsNoteTest extends TestCase { assertEquals("Wrong doze for Case " + caseName, globalDoze, bi.getScreenDozeEnergy()); } + + private void checkCustomMeasuredEnergy(String caseName, + long totalBlameA, long totalBlameB, + int uid1, long blame1A, long blame1B, + 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, + actualTotal[0]); + + assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB, + actualTotal[1]); + + assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, actualUid1[0]); + + assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, actualUid1[1]); + + assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, actualUid2[0]); + + assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]); + } } 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 b9908f46c81c..5fd5a7838c3a 100644 --- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java @@ -387,6 +387,59 @@ public class MeasuredEnergyStatsTest { } @Test + public void testIsValidCustomBucket() { + final MeasuredEnergyStats stats + = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3); + assertFalse(stats.isValidCustomBucket(-1)); + assertTrue(stats.isValidCustomBucket(0)); + assertTrue(stats.isValidCustomBucket(1)); + assertTrue(stats.isValidCustomBucket(2)); + assertFalse(stats.isValidCustomBucket(3)); + assertFalse(stats.isValidCustomBucket(4)); + + final MeasuredEnergyStats boringStats + = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0); + assertFalse(boringStats.isValidCustomBucket(-1)); + assertFalse(boringStats.isValidCustomBucket(0)); + assertFalse(boringStats.isValidCustomBucket(1)); + } + + @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/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.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml index cf0199bd1b85..c9405748d7de 100644 --- a/data/etc/car/com.android.car.developeroptions.xml +++ b/data/etc/car/com.android.car.developeroptions.xml @@ -26,6 +26,7 @@ <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/> <permission name="android.permission.DELETE_PACKAGES"/> <permission name="android.permission.FORCE_STOP_PACKAGES"/> + <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_DEBUGGING"/> <permission name="android.permission.MANAGE_FINGERPRINT"/> @@ -40,10 +41,12 @@ <permission name="android.permission.MOVE_PACKAGE"/> <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.READ_DREAM_STATE"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.READ_SEARCH_INDEXABLES"/> <permission name="android.permission.REBOOT"/> <permission name="android.permission.REQUEST_NETWORK_SCORES"/> + <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/> <permission name="android.permission.SET_TIME"/> <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.TETHER_PRIVILEGED"/> 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/car/com.android.car.xml b/data/etc/car/com.android.car.xml index 19548bc404d1..48f6ab376c82 100644 --- a/data/etc/car/com.android.car.xml +++ b/data/etc/car/com.android.car.xml @@ -25,6 +25,7 @@ <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.REBOOT"/> <permission name="android.permission.READ_LOGS"/> + <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> </privapp-permissions> </permissions> diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml index 3a20a9cb1695..bd30d7a61517 100644 --- a/data/etc/car/com.google.android.car.kitchensink.xml +++ b/data/etc/car/com.google.android.car.kitchensink.xml @@ -27,8 +27,10 @@ <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/> <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> <permission name="android.permission.LOCATION_HARDWARE"/> + <permission name="android.permission.LOCK_DEVICE"/> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MASTER_CLEAR"/> <!-- use for CarServiceTest --> <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> <permission name="android.permission.MODIFY_AUDIO_ROUTING"/> @@ -40,9 +42,11 @@ <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.READ_LOGS"/> <permission name="android.permission.REBOOT"/> + <permission name="android.permission.RESET_PASSWORD"/> <permission name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"/> <!-- use for CarServiceTest --> <permission name="android.permission.SET_ACTIVITY_WATCHER"/> + <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> <!-- use for rotary fragment to enable/disable packages related to rotary --> 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/com.android.settings.xml b/data/etc/com.android.settings.xml index e473c55ce010..3fdb0da80875 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -58,5 +58,6 @@ <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.READ_DREAM_STATE"/> <permission name="android.permission.READ_DREAM_SUPPRESSION"/> + <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/> </privapp-permissions> </permissions> diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 35e6b8595c2a..40c75a4d2f2f 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1414,6 +1414,16 @@ public class Typeface { return Arrays.binarySearch(mSupportedAxes, axis) >= 0; } + /** @hide */ + public List<FontFamily> getFallback() { + ArrayList<FontFamily> families = new ArrayList<>(); + int familySize = nativeGetFamilySize(native_instance); + for (int i = 0; i < familySize; ++i) { + families.add(new FontFamily(nativeGetFamily(native_instance, i))); + } + return families; + } + private static native long nativeCreateFromTypeface(long native_instance, int style); private static native long nativeCreateFromTypefaceWithExactStyle( long native_instance, int weight, boolean italic); @@ -1439,6 +1449,13 @@ public class Typeface { @CriticalNative private static native long nativeGetReleaseFunc(); + @CriticalNative + private static native int nativeGetFamilySize(long naitvePtr); + + @CriticalNative + private static native long nativeGetFamily(long nativePtr, int index); + + private static native void nativeRegisterGenericFamily(String str, long nativePtr); private static native int nativeWriteTypefaces( diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index 8c13d3e7e51d..a771a6e35345 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -125,7 +125,7 @@ public final class FontFamily { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback); - final FontFamily family = new FontFamily(mFonts, ptr); + final FontFamily family = new FontFamily(ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; } @@ -146,7 +146,8 @@ public final class FontFamily { private final long mNativePtr; // Use Builder instead. - private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) { + /** @hide */ + public FontFamily(long ptr) { mNativePtr = ptr; } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 904085feb8cb..255f9e659c36 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -68,44 +68,23 @@ public final class SystemFonts { */ public static @NonNull Set<Font> getAvailableFonts() { synchronized (LOCK) { - if (sAvailableFonts != null) { - return sAvailableFonts; - } - - if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { - sAvailableFonts = collectAllFonts(); - } else { + if (sAvailableFonts == null) { Set<Font> set = new ArraySet<>(); - for (FontFamily[] items : sFamilyMap.values()) { - for (FontFamily family : items) { - for (int i = 0; i < family.getSize(); ++i) { - set.add(family.getFont(i)); + for (Typeface tf : Typeface.getSystemFontMap().values()) { + List<FontFamily> families = tf.getFallback(); + for (int i = 0; i < families.size(); ++i) { + FontFamily family = families.get(i); + for (int j = 0; j < family.getSize(); ++j) { + set.add(family.getFont(j)); } } } - sAvailableFonts = Collections.unmodifiableSet(set); } return sAvailableFonts; } } - private static @NonNull Set<Font> collectAllFonts() { - // TODO: use updated fonts - FontConfig fontConfig = getSystemPreinstalledFontConfig(); - Map<String, FontFamily[]> map = buildSystemFallback(fontConfig); - - Set<Font> res = new ArraySet<>(); - for (FontFamily[] families : map.values()) { - for (FontFamily family : families) { - for (int i = 0; i < family.getSize(); ++i) { - res.add(family.getFont(i)); - } - } - } - return res; - } - private static @Nullable ByteBuffer mmap(@NonNull String fullPath) { try (FileInputStream file = new FileInputStream(fullPath)) { final FileChannel fileChannel = file.getChannel(); @@ -329,13 +308,4 @@ public final class SystemFonts { Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result); return result; } - - /** - * @hide - */ - public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) { - synchronized (LOCK) { - sFamilyMap = fallbackMap; - } - } } 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/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java index aa82339a436a..73fd6931066d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java @@ -16,24 +16,16 @@ package com.android.wm.shell; -import android.util.Slog; - -import com.android.wm.shell.apppairs.AppPairs; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.onehanded.OneHanded; -import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.common.annotations.ExternalThread; import java.io.PrintWriter; -import java.util.Optional; -import java.util.concurrent.TimeUnit; /** * An entry point into the shell for dumping shell internal state and running adb commands. * * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}. */ +@ExternalThread public interface ShellCommandHandler { /** * Dumps the shell state. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index 982cc006e331..eaed24d6195a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -18,13 +18,12 @@ package com.android.wm.shell; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT; -import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; -import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; +import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; @@ -38,24 +37,24 @@ import java.util.Optional; public final class ShellCommandHandlerImpl { private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName(); - private final Optional<LegacySplitScreen> mLegacySplitScreenOptional; + private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<Pip> mPipOptional; - private final Optional<OneHanded> mOneHandedOptional; - private final Optional<HideDisplayCutout> mHideDisplayCutout; + private final Optional<OneHandedController> mOneHandedOptional; + private final Optional<HideDisplayCutoutController> mHideDisplayCutout; + private final Optional<AppPairsController> mAppPairsOptional; private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<AppPairs> mAppPairsOptional; private final ShellExecutor mMainExecutor; private final HandlerImpl mImpl = new HandlerImpl(); public static ShellCommandHandler create( ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, - Optional<OneHanded> oneHandedOptional, - Optional<HideDisplayCutout> hideDisplayCutout, - Optional<AppPairs> appPairsOptional, + Optional<OneHandedController> oneHandedOptional, + Optional<HideDisplayCutoutController> hideDisplayCutout, + Optional<AppPairsController> appPairsOptional, ShellExecutor mainExecutor) { return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, @@ -64,12 +63,12 @@ public final class ShellCommandHandlerImpl { private ShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, - Optional<OneHanded> oneHandedOptional, - Optional<HideDisplayCutout> hideDisplayCutout, - Optional<AppPairs> appPairsOptional, + Optional<OneHandedController> oneHandedOptional, + Optional<HideDisplayCutoutController> hideDisplayCutout, + Optional<AppPairsController> appPairsOptional, ShellExecutor mainExecutor) { mShellTaskOrganizer = shellTaskOrganizer; mLegacySplitScreenOptional = legacySplitScreenOptional; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 925bf4bbb01c..7376d9898ab8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -18,13 +18,12 @@ package com.android.wm.shell; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; -import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; @@ -39,9 +38,9 @@ public class ShellInitImpl { private final DisplayImeController mDisplayImeController; private final DragAndDropController mDragAndDropController; private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<LegacySplitScreen> mLegacySplitScreenOptional; + private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; private final Optional<SplitScreenController> mSplitScreenOptional; - private final Optional<AppPairs> mAppPairsOptional; + private final Optional<AppPairsController> mAppPairsOptional; private final FullscreenTaskListener mFullscreenTaskListener; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; @@ -51,9 +50,9 @@ public class ShellInitImpl { public static ShellInit create(DisplayImeController displayImeController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, - Optional<AppPairs> appPairsOptional, + Optional<AppPairsController> appPairsOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, ShellExecutor mainExecutor) { @@ -71,9 +70,9 @@ public class ShellInitImpl { private ShellInitImpl(DisplayImeController displayImeController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, + Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, - Optional<AppPairs> appPairsOptional, + Optional<AppPairsController> appPairsOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, ShellExecutor mainExecutor) { @@ -97,7 +96,7 @@ public class ShellInitImpl { // Register the shell organizer mShellTaskOrganizer.registerOrganizer(); - mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered); + mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered); mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered); // Bind the splitscreen impl to the drag drop controller 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 a570c0af698d..b22f358c0781 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -44,7 +44,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.sizecompatui.SizeCompatUI; +import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; import java.io.PrintWriter; @@ -108,20 +108,20 @@ public class ShellTaskOrganizer extends TaskOrganizer { * compat. */ @Nullable - private final SizeCompatUI mSizeCompatUI; + private final SizeCompatUIController mSizeCompatUI; public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable - SizeCompatUI sizeCompatUI) { + SizeCompatUIController sizeCompatUI) { this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable SizeCompatUI sizeCompatUI) { + Context context, @Nullable SizeCompatUIController sizeCompatUI) { 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 @@ -342,8 +342,8 @@ public class ShellTaskOrganizer extends TaskOrganizer { } /** - * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update - * the UI accordingly. + * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task + * to update the UI accordingly. * * @param taskInfo the new Task info * @param taskListener listener to handle the Task Surface placement. {@code null} if task is diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java index a5dd79b373bd..58ca1fbaba24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; public class TaskViewFactoryController { private final ShellTaskOrganizer mTaskOrganizer; private final ShellExecutor mShellExecutor; + private final TaskViewFactory mImpl = new TaskViewFactoryImpl(); public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer, ShellExecutor shellExecutor) { @@ -37,8 +38,11 @@ public class TaskViewFactoryController { mShellExecutor = shellExecutor; } + public TaskViewFactory asTaskViewFactory() { + return mImpl; + } + /** Creates an {@link TaskView} */ - @ShellMainThread public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) { TaskView taskView = new TaskView(context, mTaskOrganizer); executor.execute(() -> { @@ -46,10 +50,6 @@ public class TaskViewFactoryController { }); } - public TaskViewFactory getTaskViewFactory() { - return new TaskViewFactoryImpl(); - } - private class TaskViewFactoryImpl implements TaskViewFactory { @ExternalThread public void create(@UiContext Context context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java index abd92577c1d4..59271e9fb63c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java @@ -27,6 +27,7 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder.PinnedStackListener /** * The singleton wrapper to communicate between WindowManagerService and WMShell features * (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc) + * TODO: Remove once PinnedStackListenerForwarder can be removed */ public class WindowManagerShellWrapper { private static final String TAG = WindowManagerShellWrapper.class.getSimpleName(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java index f5aa852c87ae..a9b1dbc3c23b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java @@ -35,8 +35,4 @@ public interface AppPairs { boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2); /** Unpairs any app-pair containing this task id. */ void unpair(int taskId); - /** Dumps current status of app pairs. */ - void dump(@NonNull PrintWriter pw, String prefix); - /** Called when the shell organizer has been registered. */ - void onOrganizerRegistered(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java index e380426b9ca2..0415f12496f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java @@ -51,18 +51,7 @@ public class AppPairsController { private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>(); private final DisplayController mDisplayController; - /** - * Creates {@link AppPairs}, returns {@code null} if the feature is not supported. - */ - @Nullable - public static AppPairs create(ShellTaskOrganizer organizer, - SyncTransactionQueue syncQueue, DisplayController displayController, - ShellExecutor mainExecutor) { - return new AppPairsController(organizer, syncQueue, displayController, - mainExecutor).mImpl; - } - - AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, + public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, DisplayController displayController, ShellExecutor mainExecutor) { mTaskOrganizer = organizer; mSyncQueue = syncQueue; @@ -70,18 +59,22 @@ public class AppPairsController { mMainExecutor = mainExecutor; } - void onOrganizerRegistered() { + public AppPairs asAppPairs() { + return mImpl; + } + + public void onOrganizerRegistered() { if (mPairsPool == null) { setPairsPool(new AppPairsPool(this)); } } @VisibleForTesting - void setPairsPool(AppPairsPool pool) { + public void setPairsPool(AppPairsPool pool) { mPairsPool = pool; } - boolean pair(int taskId1, int taskId2) { + public boolean pair(int taskId1, int taskId2) { final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1); final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2); if (task1 == null || task2 == null) { @@ -90,13 +83,13 @@ public class AppPairsController { return pair(task1, task2); } - boolean pair(ActivityManager.RunningTaskInfo task1, + public boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) { return pairInner(task1, task2) != null; } @VisibleForTesting - AppPair pairInner( + public AppPair pairInner( @NonNull ActivityManager.RunningTaskInfo task1, @NonNull ActivityManager.RunningTaskInfo task2) { final AppPair pair = mPairsPool.acquire(); @@ -109,11 +102,11 @@ public class AppPairsController { return pair; } - void unpair(int taskId) { + public void unpair(int taskId) { unpair(taskId, true /* releaseToPool */); } - void unpair(int taskId, boolean releaseToPool) { + public void unpair(int taskId, boolean releaseToPool) { AppPair pair = mActiveAppPairs.get(taskId); if (pair == null) { for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) { @@ -137,19 +130,19 @@ public class AppPairsController { } } - ShellTaskOrganizer getTaskOrganizer() { + public ShellTaskOrganizer getTaskOrganizer() { return mTaskOrganizer; } - SyncTransactionQueue getSyncTransactionQueue() { + public SyncTransactionQueue getSyncTransactionQueue() { return mSyncQueue; } - DisplayController getDisplayController() { + public DisplayController getDisplayController() { return mDisplayController; } - private void dump(@NonNull PrintWriter pw, String prefix) { + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; pw.println(prefix + this); @@ -202,21 +195,5 @@ public class AppPairsController { AppPairsController.this.unpair(taskId); }); } - - @Override - public void onOrganizerRegistered() { - mMainExecutor.execute(() -> { - AppPairsController.this.onOrganizerRegistered(); - }); - } - - @Override - public void dump(@NonNull PrintWriter pw, String prefix) { - try { - mMainExecutor.executeBlocking(() -> AppPairsController.this.dump(pw, prefix)); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to dump AppPairsController in 2s"); - } - } } } 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 d73fc6dca4c6..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; @@ -192,9 +193,9 @@ public class BubbleController { private boolean mIsStatusBarShade = true; /** - * Injected constructor. + * Creates an instance of the BubbleController. */ - public static Bubbles create(Context context, + public static BubbleController create(Context context, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, @Nullable IStatusBarService statusBarService, @@ -211,14 +212,14 @@ public class BubbleController { return new BubbleController(context, data, synchronizer, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, launcherApps, - logger, organizer, positioner, mainExecutor, mainHandler).mImpl; + logger, organizer, positioner, mainExecutor, mainHandler); } /** * Testing constructor. */ @VisibleForTesting - public BubbleController(Context context, + protected BubbleController(Context context, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, @@ -322,7 +323,7 @@ public class BubbleController { } @VisibleForTesting - public Bubbles getImpl() { + public Bubbles asBubbles() { return mImpl; } @@ -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/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 21004280c952..2f31acd41408 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -163,6 +163,7 @@ public class BubbleExpandedView extends LinearLayout { // Apply flags to make behaviour match documentLaunchMode=always. fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + fillInIntent.putExtra(Intent.EXTRA_IS_BUBBLED, true); if (mBubble != null) { mBubble.setIntentActive(); } 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/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java index 3a2f0da6bf03..60123ab97fd7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java @@ -31,12 +31,6 @@ import java.io.PrintWriter; public interface HideDisplayCutout { /** * Notifies {@link Configuration} changed. - * @param newConfig */ void onConfigurationChanged(Configuration newConfig); - - /** - * Dumps hide display cutout status. - */ - void dump(@NonNull PrintWriter pw); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java index 12b8b87f1285..23f76ca5f6ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java @@ -44,20 +44,12 @@ public class HideDisplayCutoutController { @VisibleForTesting boolean mEnabled; - HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer, - ShellExecutor mainExecutor) { - mContext = context; - mOrganizer = organizer; - mMainExecutor = mainExecutor; - updateStatus(); - } - /** * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not * supported. */ @Nullable - public static HideDisplayCutout create( + public static HideDisplayCutoutController create( Context context, DisplayController displayController, ShellExecutor mainExecutor) { // The SystemProperty is set for devices that support this feature and is used to control // whether to create the HideDisplayCutout instance. @@ -68,7 +60,19 @@ public class HideDisplayCutoutController { HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(context, displayController, mainExecutor); - return new HideDisplayCutoutController(context, organizer, mainExecutor).mImpl; + return new HideDisplayCutoutController(context, organizer, mainExecutor); + } + + HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer, + ShellExecutor mainExecutor) { + mContext = context; + mOrganizer = organizer; + mMainExecutor = mainExecutor; + updateStatus(); + } + + public HideDisplayCutout asHideDisplayCutout() { + return mImpl; } @VisibleForTesting @@ -94,7 +98,7 @@ public class HideDisplayCutoutController { updateStatus(); } - private void dump(@NonNull PrintWriter pw) { + public void dump(@NonNull PrintWriter pw) { final String prefix = " "; pw.print(TAG); pw.println(" states: "); @@ -111,14 +115,5 @@ public class HideDisplayCutoutController { HideDisplayCutoutController.this.onConfigurationChanged(newConfig); }); } - - @Override - public void dump(@NonNull PrintWriter pw) { - try { - mMainExecutor.executeBlocking(() -> HideDisplayCutoutController.this.dump(pw)); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to dump HideDisplayCutoutController in 2s"); - } - } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java index bca6deb451c9..d25bef197359 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java @@ -115,21 +115,6 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays private volatile boolean mAdjustedForIme = false; private boolean mHomeStackResizable = false; - /** - * Creates {@link SplitScreen}, returns {@code null} if the feature is not supported. - */ - @Nullable - public static LegacySplitScreen create(Context context, - DisplayController displayController, SystemWindows systemWindows, - DisplayImeController imeController, TransactionPool transactionPool, - ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, - TaskStackListenerImpl taskStackListener, Transitions transitions, - ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) { - return new LegacySplitScreenController(context, displayController, systemWindows, - imeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener, - transitions, mainExecutor, sfVsyncAnimationHandler).mImpl; - } - public LegacySplitScreenController(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController imeController, TransactionPool transactionPool, @@ -228,8 +213,12 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } }); } + + public LegacySplitScreen asLegacySplitScreen() { + return mImpl; + } - void onSplitScreenSupported() { + public void onSplitScreenSupported() { // Set starting tile bounds based on middle target final WindowContainerTransaction tct = new WindowContainerTransaction(); int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; @@ -237,7 +226,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays mTaskOrganizer.applyTransaction(tct); } - private void onKeyguardVisibilityChanged(boolean showing) { + public void onKeyguardVisibilityChanged(boolean showing) { if (!isSplitActive() || mView == null) { return; } @@ -293,19 +282,19 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - boolean isMinimized() { + public boolean isMinimized() { return mMinimized; } - boolean isHomeStackResizable() { + public boolean isHomeStackResizable() { return mHomeStackResizable; } - DividerView getDividerView() { + public DividerView getDividerView() { return mView; } - boolean isDividerVisible() { + public boolean isDividerVisible() { return mView != null && mView.getVisibility() == View.VISIBLE; } @@ -314,13 +303,13 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays * isDividerVisible because the divider is only visible once *everything* is in split mode * while this only cares if some things are (eg. while entering/exiting as well). */ - private boolean isSplitActive() { + public boolean isSplitActive() { return mSplits.mPrimary != null && mSplits.mSecondary != null && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED); } - private void addDivider(Configuration configuration) { + public void addDivider(Configuration configuration) { Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId()); mView = (DividerView) LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null); @@ -338,14 +327,14 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays mWindowManager.add(mView, width, height, mContext.getDisplayId()); } - private void removeDivider() { + public void removeDivider() { if (mView != null) { mView.onDividerRemoved(); } mWindowManager.remove(); } - private void update(Configuration configuration) { + public void update(Configuration configuration) { final boolean isDividerHidden = mView != null && mIsKeyguardShowing; removeDivider(); @@ -358,11 +347,11 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays mView.setHidden(isDividerHidden); } - void onTaskVanished() { + public void onTaskVanished() { removeDivider(); } - private void updateVisibility(final boolean visible) { + public void updateVisibility(final boolean visible) { if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible); if (mVisible != visible) { mVisible = visible; @@ -390,7 +379,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - private void setMinimized(final boolean minimized) { + public void setMinimized(final boolean minimized) { if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible); mMainExecutor.execute(() -> { if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible); @@ -401,7 +390,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays }); } - private void setHomeMinimized(final boolean minimized) { + public void setHomeMinimized(final boolean minimized) { if (DEBUG) { Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:" + mHomeStackResizable + " split:" + isDividerVisible()); @@ -441,7 +430,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - void setAdjustedForIme(boolean adjustedForIme) { + public void setAdjustedForIme(boolean adjustedForIme) { if (mAdjustedForIme == adjustedForIme) { return; } @@ -449,30 +438,30 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays updateTouchable(); } - private void updateTouchable() { + public void updateTouchable() { mWindowManager.setTouchable(!mAdjustedForIme); } - private void onUndockingTask() { + public void onUndockingTask() { if (mView != null) { mView.onUndockingTask(); } } - private void onAppTransitionFinished() { + public void onAppTransitionFinished() { if (mView == null) { return; } mForcedResizableController.onAppTransitionFinished(); } - private void dump(PrintWriter pw) { + public void dump(PrintWriter pw) { pw.print(" mVisible="); pw.println(mVisible); pw.print(" mMinimized="); pw.println(mMinimized); pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme); } - long getAnimDuration() { + public long getAnimDuration() { float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat( @@ -482,14 +471,14 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays return (long) (transitionDuration * transitionScale); } - void registerInSplitScreenListener(Consumer<Boolean> listener) { + public void registerInSplitScreenListener(Consumer<Boolean> listener) { listener.accept(isDividerVisible()); synchronized (mDockedStackExistsListeners) { mDockedStackExistsListeners.add(new WeakReference<>(listener)); } } - void unregisterInSplitScreenListener(Consumer<Boolean> listener) { + public void unregisterInSplitScreenListener(Consumer<Boolean> listener) { synchronized (mDockedStackExistsListeners) { for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) { if (mDockedStackExistsListeners.get(i) == listener) { @@ -499,13 +488,13 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - private void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { + public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { synchronized (mBoundsChangedListeners) { mBoundsChangedListeners.add(new WeakReference<>(listener)); } } - private boolean splitPrimaryTask() { + public boolean splitPrimaryTask() { try { if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED || isSplitActive()) { @@ -538,12 +527,12 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays topRunningTask.taskId, true /* onTop */); } - private void dismissSplitToPrimaryTask() { + public void dismissSplitToPrimaryTask() { startDismissSplit(true /* toPrimaryTask */); } /** Notifies the bounds of split screen changed. */ - void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) { + public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) { synchronized (mBoundsChangedListeners) { mBoundsChangedListeners.removeIf(wf -> { BiConsumer<Rect, Rect> l = wf.get(); @@ -553,19 +542,19 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - void startEnterSplit() { + public void startEnterSplit() { update(mDisplayController.getDisplayContext( mContext.getDisplayId()).getResources().getConfiguration()); // Set resizable directly here because applyEnterSplit already resizes home stack. mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout); } - void prepareEnterSplitTransition(WindowContainerTransaction outWct) { + public void prepareEnterSplitTransition(WindowContainerTransaction outWct) { // Set resizable directly here because buildEnterSplit already resizes home stack. mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout); } - void finishEnterSplitTransition(boolean minimized) { + public void finishEnterSplitTransition(boolean minimized) { update(mDisplayController.getDisplayContext( mContext.getDisplayId()).getResources().getConfiguration()); if (minimized) { @@ -575,11 +564,11 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - void startDismissSplit(boolean toPrimaryTask) { + public void startDismissSplit(boolean toPrimaryTask) { startDismissSplit(toPrimaryTask, false /* snapped */); } - void startDismissSplit(boolean toPrimaryTask, boolean snapped) { + public void startDismissSplit(boolean toPrimaryTask, boolean snapped) { if (Transitions.ENABLE_SHELL_TRANSITIONS) { mSplits.getSplitTransitions().dismissSplit( mSplits, mSplitLayout, !toPrimaryTask, snapped); @@ -589,7 +578,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - void onDismissSplit() { + public void onDismissSplit() { updateVisibility(false /* visible */); mMinimized = false; // Resets divider bar position to undefined, so new divider bar will apply default position @@ -599,7 +588,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays mImePositionProcessor.reset(); } - void ensureMinimizedSplit() { + public void ensureMinimizedSplit() { setHomeMinimized(true /* minimized */); if (mView != null && !isDividerVisible()) { // Wasn't in split-mode yet, so enter now. @@ -610,7 +599,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - void ensureNormalSplit() { + public void ensureNormalSplit() { setHomeMinimized(false /* minimized */); if (mView != null && !isDividerVisible()) { // Wasn't in split-mode, so enter now. @@ -621,15 +610,15 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays } } - LegacySplitDisplayLayout getSplitLayout() { + public LegacySplitDisplayLayout getSplitLayout() { return mSplitLayout; } - WindowManagerProxy getWmProxy() { + public WindowManagerProxy getWmProxy() { return mWindowManagerProxy; } - WindowContainerToken getSecondaryRoot() { + public WindowContainerToken getSecondaryRoot() { if (mSplits == null || mSplits.mSecondary == null) { return null; } 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/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index e95864873c0c..11c11f44a781 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -69,9 +69,4 @@ public interface OneHanded { * 3 button navigation mode only */ void registerGestureCallback(OneHandedGestureEventCallback callback); - - /** - * Dump one handed status. - */ - void dump(@NonNull PrintWriter pw); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index eaa704f22410..5a3c38b09ec6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -134,10 +134,10 @@ public class OneHandedController { /** - * Creates {@link OneHanded}, returns {@code null} if the feature is not supported. + * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported. */ @Nullable - public static OneHanded create( + public static OneHandedController create( Context context, DisplayController displayController, TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) { @@ -166,7 +166,7 @@ public class OneHandedController { return new OneHandedController(context, displayController, oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler, gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager, - taskStackListener, mainExecutor, mainHandler).mImpl; + taskStackListener, mainExecutor, mainHandler); } @VisibleForTesting @@ -228,6 +228,10 @@ public class OneHandedController { mAccessibilityStateChangeListener); } + public OneHanded asOneHanded() { + return mImpl; + } + /** * Set one handed enabled or disabled when user update settings */ @@ -468,7 +472,7 @@ public class OneHandedController { } } - private void dump(@NonNull PrintWriter pw) { + public void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG + "states: "); pw.print(innerPrefix + "mOffSetFraction="); @@ -561,12 +565,5 @@ public class OneHandedController { OneHandedController.this.registerGestureCallback(callback); }); } - - @Override - public void dump(@NonNull PrintWriter pw) { - mMainExecutor.execute(() -> { - OneHandedController.this.dump(pw); - }); - } } } 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 ad6f435ea907..3064af6f5170 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 @@ -67,6 +67,7 @@ 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; @@ -131,7 +132,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final PipUiEventLogger mPipUiEventLoggerLogger; private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private final Optional<LegacySplitScreen> mSplitScreenOptional; + private final Optional<LegacySplitScreenController> mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; protected final ShellExecutor mMainExecutor; @@ -207,7 +208,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, @NonNull PipTransitionController pipTransitionController, - Optional<LegacySplitScreen> splitScreenOptional, + Optional<LegacySplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -1047,7 +1048,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen. + * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split + * screen. * * @param destinationBoundsOut contain the updated destination bounds if applicable * @return {@code true} if destinationBounds is altered for split screen @@ -1057,7 +1059,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return false; } - LegacySplitScreen legacySplitScreen = mSplitScreenOptional.get(); + LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get(); if (!legacySplitScreen.isDividerVisible()) { // fail early if system is not in split screen mode return false; 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/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java deleted file mode 100644 index 11f22ed24a69..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.sizecompatui; - -import android.annotation.Nullable; -import android.graphics.Rect; -import android.os.IBinder; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.annotations.ExternalThread; - -/** - * Interface to engage size compat mode UI. - */ -@ExternalThread -public interface SizeCompatUI { - /** - * 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 taskBounds task bounds to place the restart button in. - * @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. - */ - void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, - @Nullable IBinder sizeCompatActivity, - @Nullable ShellTaskOrganizer.TaskListener taskListener); -} 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 286c3b6a051e..48ee86c4954f 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 @@ -48,7 +48,6 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); @VisibleForTesting - final SizeCompatUI mImpl = new SizeCompatUIImpl(); private final Context mContext; private final ShellExecutor mMainExecutor; private final DisplayController mDisplayController; @@ -57,17 +56,8 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang /** Only show once automatically in the process life. */ private boolean mHasShownHint; - /** Creates the {@link SizeCompatUIController}. */ - public static SizeCompatUI create(Context context, - DisplayController displayController, - DisplayImeController imeController, - ShellExecutor mainExecutor) { - return new SizeCompatUIController(context, displayController, imeController, mainExecutor) - .mImpl; - } - @VisibleForTesting - SizeCompatUIController(Context context, + public SizeCompatUIController(Context context, DisplayController displayController, DisplayImeController imeController, ShellExecutor mainExecutor) { @@ -79,7 +69,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang mImeController.addPositionProcessor(this); } - private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, + public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, @Nullable IBinder sizeCompatActivity, @Nullable ShellTaskOrganizer.TaskListener taskListener) { // TODO Draw button on Task surface @@ -177,15 +167,4 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang } return context; } - - private class SizeCompatUIImpl implements SizeCompatUI { - @Override - public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, - @Nullable IBinder sizeCompatActivity, - @Nullable ShellTaskOrganizer.TaskListener taskListener) { - mMainExecutor.execute(() -> - SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId, - taskBounds, sizeCompatActivity, taskListener)); - } - } } 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 80ea9b9e177e..176b33dda020 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 @@ -51,7 +51,7 @@ import androidx.test.filters.SmallTest; 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.SizeCompatUI; +import com.android.wm.shell.sizecompatui.SizeCompatUIController; import org.junit.Before; import org.junit.Test; @@ -76,7 +76,7 @@ public class ShellTaskOrganizerTests { @Mock private Context mContext; @Mock - private SizeCompatUI mSizeCompatUI; + private SizeCompatUIController mSizeCompatUI; ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 9430af946899..d10c03677d30 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -44,6 +44,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PhonePipMenuController; import org.junit.Before; @@ -70,7 +71,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipTransitionController mMockPipTransitionController; @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; - @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen; + @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; private TestShellExecutor mMainExecutor; private PipBoundsState mPipBoundsState; 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 98f01ff08deb..0eb64e5963d1 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 @@ -86,7 +86,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { final Rect taskBounds = new Rect(0, 0, 1000, 2000); // Verify that the restart button is added with non-null size compat activity. - mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, + mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, mMockActivityToken, mMockTaskListener); mShellMainExecutor.flushAll(); @@ -94,7 +94,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken)); // Verify that the restart button is removed with null size compat activity. - mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null); + mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null); mShellMainExecutor.flushAll(); verify(mMockButton).remove(); @@ -104,7 +104,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { public void testChangeButtonVisibilityOnImeShowHide() { final int taskId = 12; final Rect taskBounds = new Rect(0, 0, 1000, 2000); - mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, + mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds, mMockActivityToken, mMockTaskListener); mShellMainExecutor.flushAll(); diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index 8f455fe4ab43..18423562bc56 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -355,29 +355,41 @@ static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring f env->SetStaticObjectField(cls, fid, typeface); } +// Critical Native +static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { + return toTypeface(faceHandle)->fFontCollection->getFamilies().size(); +} + +// Critical Native +static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) { + std::shared_ptr<minikin::FontFamily> family = + toTypeface(faceHandle)->fFontCollection->getFamilies()[index]; + return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family))); +} /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gTypefaceMethods[] = { - { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface }, - { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J", - (void*)Typeface_createFromTypefaceWithExactStyle }, - { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J", - (void*)Typeface_createFromTypefaceWithVariation }, - { "nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias }, - { "nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc }, - { "nativeGetStyle", "(J)I", (void*)Typeface_getStyle }, - { "nativeGetWeight", "(J)I", (void*)Typeface_getWeight }, - { "nativeCreateFromArray", "([JJII)J", - (void*)Typeface_createFromArray }, - { "nativeSetDefault", "(J)V", (void*)Typeface_setDefault }, - { "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes }, - { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V", - (void*)Typeface_registerGenericFamily }, - { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces}, - { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces}, - { "nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V", - (void*)Typeface_forceSetStaticFinalField }, + {"nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface}, + {"nativeCreateFromTypefaceWithExactStyle", "(JIZ)J", + (void*)Typeface_createFromTypefaceWithExactStyle}, + {"nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J", + (void*)Typeface_createFromTypefaceWithVariation}, + {"nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias}, + {"nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc}, + {"nativeGetStyle", "(J)I", (void*)Typeface_getStyle}, + {"nativeGetWeight", "(J)I", (void*)Typeface_getWeight}, + {"nativeCreateFromArray", "([JJII)J", (void*)Typeface_createFromArray}, + {"nativeSetDefault", "(J)V", (void*)Typeface_setDefault}, + {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes}, + {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V", + (void*)Typeface_registerGenericFamily}, + {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces}, + {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces}, + {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V", + (void*)Typeface_forceSetStaticFinalField}, + {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize}, + {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily}, }; int register_android_graphics_Typeface(JNIEnv* env) diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp index d291ec001daf..438a92ef8fb5 100644 --- a/libs/incident/Android.bp +++ b/libs/incident/Android.bp @@ -95,7 +95,7 @@ cc_test { name: "libincident_test", test_config: "AndroidTest.xml", defaults: ["libincidentpriv_defaults"], - test_suites: ["device-tests", "mts"], + test_suites: ["device-tests", "mts-statsd"], compile_multilib: "both", multilib: { lib64: { diff --git a/media/java/android/media/DrmInitData.java b/media/java/android/media/DrmInitData.java index 85b4ba54de63..3c48f8f602df 100644 --- a/media/java/android/media/DrmInitData.java +++ b/media/java/android/media/DrmInitData.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.media.MediaDrm; import java.util.Arrays; +import java.util.Objects; import java.util.UUID; /** @@ -94,9 +95,9 @@ public abstract class DrmInitData { * @param data The initialization data. */ public SchemeInitData(@NonNull UUID uuid, @NonNull String mimeType, @NonNull byte[] data) { - this.uuid = uuid; - this.mimeType = mimeType; - this.data = data; + this.uuid = Objects.requireNonNull(uuid); + this.mimeType = Objects.requireNonNull(mimeType); + this.data = Objects.requireNonNull(data); } @Override diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index 02fa94c845a6..5a7ff7fbd1ae 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -33,7 +33,8 @@ interface IRingtonePlayer { float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig); oneway void stop(IBinder token); boolean isPlaying(IBinder token); - oneway void setPlaybackProperties(IBinder token, float volume, boolean looping); + oneway void setPlaybackProperties(IBinder token, float volume, boolean looping, + boolean hapticGeneratorEnabled); /** Used for Notification sound playback. */ oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa); diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index bd783ce9f6b2..79d505ebbde8 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.Resources.NotFoundException; import android.database.Cursor; +import android.media.audiofx.HapticGenerator; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -77,6 +78,7 @@ public class Ringtone { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private MediaPlayer mLocalPlayer; private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener(); + private HapticGenerator mHapticGenerator; @UnsupportedAppUsage private Uri mUri; @@ -89,6 +91,7 @@ public class Ringtone { // playback properties, use synchronized with mPlaybackSettingsLock private boolean mIsLooping = false; private float mVolume = 1.0f; + private boolean mHapticGeneratorEnabled = false; private final Object mPlaybackSettingsLock = new Object(); /** {@hide} */ @@ -197,15 +200,50 @@ public class Ringtone { } /** + * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can + * only be enabled on devices that support the effect. + * + * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false. + * @see android.media.audiofx.HapticGenerator#isAvailable() + */ + public boolean setHapticGeneratorEnabled(boolean enabled) { + if (!HapticGenerator.isAvailable()) { + return false; + } + synchronized (mPlaybackSettingsLock) { + mHapticGeneratorEnabled = enabled; + applyPlaybackProperties_sync(); + } + return true; + } + + /** + * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not. + * @return true if the HapticGenerator is enabled. + */ + public boolean isHapticGeneratorEnabled() { + synchronized (mPlaybackSettingsLock) { + return mHapticGeneratorEnabled; + } + } + + /** * Must be called synchronized on mPlaybackSettingsLock */ private void applyPlaybackProperties_sync() { if (mLocalPlayer != null) { mLocalPlayer.setVolume(mVolume); mLocalPlayer.setLooping(mIsLooping); + if (mHapticGenerator == null && mHapticGeneratorEnabled) { + mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId()); + } + if (mHapticGenerator != null) { + mHapticGenerator.setEnabled(mHapticGeneratorEnabled); + } } else if (mAllowRemote && (mRemotePlayer != null)) { try { - mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping); + mRemotePlayer.setPlaybackProperties( + mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled); } catch (RemoteException e) { Log.w(TAG, "Problem setting playback properties: ", e); } @@ -413,6 +451,10 @@ public class Ringtone { private void destroyLocalPlayer() { if (mLocalPlayer != null) { + if (mHapticGenerator != null) { + mHapticGenerator.release(); + mHapticGenerator = null; + } mLocalPlayer.setOnCompletionListener(null); mLocalPlayer.reset(); mLocalPlayer.release(); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 98b9ad8ec2b3..740bc2dbeb43 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -2526,6 +2526,7 @@ public final class TvInputManager { * Pauses TV program recording in the current recording session. * * @param params A set of extra parameters which might be handled with this event. + * {@link TvRecordingClient#pauseRecording(Bundle)}. */ void pauseRecording(@NonNull Bundle params) { if (mToken == null) { @@ -2543,6 +2544,7 @@ public final class TvInputManager { * Resumes TV program recording in the current recording session. * * @param params A set of extra parameters which might be handled with this event. + * {@link TvRecordingClient#resumeRecording(Bundle)}. */ void resumeRecording(@NonNull Bundle params) { if (mToken == null) { diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 65b64d7e8df3..4972529eb705 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -180,6 +180,10 @@ cc_library_shared { "libstagefright_foundation_headers", ], + // TunerService is a system service required for Tuner feature. + // TunerJNI is a client of TunerService so we build the dependency here. + required: ["mediatuner"], + export_include_dirs: ["."], cflags: [ diff --git a/media/jni/tuner/ClientHelper.h b/media/jni/tuner/ClientHelper.h index 185b2f651d29..508dccf36841 100644 --- a/media/jni/tuner/ClientHelper.h +++ b/media/jni/tuner/ClientHelper.h @@ -19,6 +19,7 @@ #include <android/binder_parcel_utils.h> #include <android/hardware/tv/tuner/1.1/types.h> +#include <utils/Log.h> using Status = ::ndk::ScopedAStatus; @@ -37,6 +38,7 @@ public: } else if (s.isOk()) { return Result::SUCCESS; } + ALOGE("Aidl exception code %s", s.getDescription().c_str()); return Result::UNKNOWN_ERROR; } }; diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp index 8b4ca371056e..f61889035432 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -43,6 +43,7 @@ using ::android::hardware::tv::tuner::V1_0::DemuxStreamId; using ::android::hardware::tv::tuner::V1_0::DemuxTpid; using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings; using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType; +using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent; using ::android::hardware::tv::tuner::V1_1::ScramblingStatus; namespace android { @@ -480,7 +481,7 @@ void FilterClient::getAidlIpAddress(DemuxIpAddress ipAddr, case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: { int size = ipAddr.srcIpAddress.v4().size(); srcIpAddress.isIpV6 = false; - srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size()); + srcIpAddress.addr.resize(size); copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size], srcIpAddress.addr.begin()); break; @@ -493,8 +494,6 @@ void FilterClient::getAidlIpAddress(DemuxIpAddress ipAddr, srcIpAddress.addr.begin()); break; } - default: - break; } switch (ipAddr.dstIpAddress.getDiscriminator()) { case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: { @@ -513,8 +512,6 @@ void FilterClient::getAidlIpAddress(DemuxIpAddress ipAddr, dstIpAddress.addr.begin()); break; } - default: - break; } } @@ -696,8 +693,6 @@ void TunerFilterCallback::getHidlFilterEvent(const vector<TunerFilterEvent>& fil getHidlRestartEvent(filterEvents, eventExt); break; } - default: - break; } } @@ -883,19 +878,18 @@ void TunerFilterCallback::getHidlMonitorEvent(const vector<TunerFilterEvent>& fi DemuxFilterEventExt& eventExt) { auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>(); eventExt.events.resize(1); + DemuxFilterMonitorEvent monitorEvent; switch (monitor.getTag()) { case TunerFilterMonitorEvent::scramblingStatus: { - eventExt.events[0].monitorEvent().scramblingStatus( - static_cast<ScramblingStatus>(monitor.scramblingStatus)); + monitorEvent.scramblingStatus(static_cast<ScramblingStatus>(monitor.scramblingStatus)); + eventExt.events[0].monitorEvent(monitorEvent); break; } case TunerFilterMonitorEvent::cid: { - eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid)); + monitorEvent.cid(static_cast<uint32_t>(monitor.cid)); + eventExt.events[0].monitorEvent(monitorEvent); break; } - default: - eventExt.events[0].noinit(); - break; } } diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp index 3a00133c69e2..0613223bd5dc 100644 --- a/media/jni/tuner/FrontendClient.cpp +++ b/media/jni/tuner/FrontendClient.cpp @@ -49,9 +49,12 @@ using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtBandwidth; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation; +using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus; using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo; +using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo; using ::android::hardware::tv::tuner::V1_0::LnbVoltage; using ::android::hardware::tv::tuner::V1_1::Constant; +using ::android::hardware::tv::tuner::V1_1::FrontendBandwidth; using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode; using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth; using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval; @@ -61,19 +64,22 @@ using ::android::hardware::tv::tuner::V1_1::FrontendDtmbTransmissionMode; using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth; using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation; using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode; +using ::android::hardware::tv::tuner::V1_1::FrontendGuardInterval; +using ::android::hardware::tv::tuner::V1_1::FrontendInterleaveMode; using ::android::hardware::tv::tuner::V1_1::FrontendModulation; +using ::android::hardware::tv::tuner::V1_1::FrontendRollOff; using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion; +using ::android::hardware::tv::tuner::V1_1::FrontendTransmissionMode; using ::android::hardware::tv::tuner::V1_1::FrontendType; namespace android { /////////////// FrontendClient /////////////////////// -FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type) { +FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) { mTunerFrontend = tunerFrontend; mAidlCallback = NULL; mHidlCallback = NULL; - mId = id; mType = type; } @@ -104,6 +110,11 @@ void FrontendClient::setHidlFrontend(sp<IFrontend> frontend) { mFrontend_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFrontend); } +// TODO: move after migration is done +void FrontendClient::setId(int id) { + mId = id; +} + Result FrontendClient::tune(const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1) { if (mTunerFrontend != NULL) { @@ -333,13 +344,26 @@ shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() { } int FrontendClient::getId() { - return mId; + if (mTunerFrontend != NULL) { + Status s = mTunerFrontend->getFrontendId(&mId); + if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) { + return mId; + } + ALOGE("Failed to getFrontendId from Tuner Frontend"); + return -1; + } + + if (mFrontend != NULL) { + return mId; + } + + return -1; } vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) { vector<FrontendStatus> hidlStatus; for (TunerFrontendStatus s : aidlStatus) { - FrontendStatus status; + FrontendStatus status = FrontendStatus(); switch (s.getTag()) { case TunerFrontendStatus::isDemodLocked: { status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>()); @@ -389,25 +413,31 @@ vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus> } case TunerFrontendStatus::modulation: { auto aidlMod = s.get<TunerFrontendStatus::modulation>(); + FrontendModulationStatus modulation; switch (mType) { case (int)FrontendType::DVBC: - status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod)); + modulation.dvbc(static_cast<FrontendDvbcModulation>(aidlMod)); + status.modulation(modulation); hidlStatus.push_back(status); break; case (int)FrontendType::DVBS: - status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod)); + modulation.dvbs(static_cast<FrontendDvbsModulation>(aidlMod)); + status.modulation(modulation); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBS: - status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod)); + modulation.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod)); + status.modulation(modulation); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBS3: - status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod)); + modulation.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod)); + status.modulation(modulation); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBT: - status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod)); + modulation.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod)); + status.modulation(modulation); hidlStatus.push_back(status); break; default: @@ -466,7 +496,7 @@ vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus> } case TunerFrontendStatus::hierarchy: { status.hierarchy(static_cast<FrontendDvbtHierarchy>( - s.get<TunerFrontendStatus::freqOffset>())); + s.get<TunerFrontendStatus::hierarchy>())); hidlStatus.push_back(status); break; } @@ -477,15 +507,16 @@ vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus> } case TunerFrontendStatus::plpInfo: { int size = s.get<TunerFrontendStatus::plpInfo>().size(); - status.plpInfo().resize(size); + hidl_vec<FrontendStatusAtsc3PlpInfo> info(size); for (int i = 0; i < size; i++) { auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i]; - status.plpInfo()[i] = { + info[i] = { .plpId = (uint8_t)aidlInfo.plpId, .isLocked = aidlInfo.isLocked, .uec = (uint32_t)aidlInfo.uec, }; } + status.plpInfo(info); hidlStatus.push_back(status); break; } @@ -503,52 +534,54 @@ vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( FrontendStatusExt1_1 status; switch (s.getTag()) { case TunerFrontendStatus::modulations: { + vector<FrontendModulation> ms; for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) { - int size = status.modulations().size(); - status.modulations().resize(size + 1); + FrontendModulation m; switch (mType) { case (int)FrontendType::DVBC: - status.modulations()[size].dvbc( - static_cast<FrontendDvbcModulation>(aidlMod)); + m.dvbc(static_cast<FrontendDvbcModulation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::DVBS: - status.modulations()[size].dvbs( - static_cast<FrontendDvbsModulation>(aidlMod)); + m.dvbs(static_cast<FrontendDvbsModulation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::DVBT: - status.modulations()[size].dvbt( - static_cast<FrontendDvbtConstellation>(aidlMod)); + m.dvbt(static_cast<FrontendDvbtConstellation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::ISDBS: - status.modulations()[size].isdbs( - static_cast<FrontendIsdbsModulation>(aidlMod)); + m.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::ISDBS3: - status.modulations()[size].isdbs3( - static_cast<FrontendIsdbs3Modulation>(aidlMod)); + m.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::ISDBT: - status.modulations()[size].isdbt( - static_cast<FrontendIsdbtModulation>(aidlMod)); + m.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::ATSC: - status.modulations()[size].atsc( - static_cast<FrontendAtscModulation>(aidlMod)); + m.atsc(static_cast<FrontendAtscModulation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::ATSC3: - status.modulations()[size].atsc3( - static_cast<FrontendAtsc3Modulation>(aidlMod)); + m.atsc3(static_cast<FrontendAtsc3Modulation>(aidlMod)); + ms.push_back(m); break; case (int)FrontendType::DTMB: - status.modulations()[size].dtmb( - static_cast<FrontendDtmbModulation>(aidlMod)); + m.dtmb(static_cast<FrontendDtmbModulation>(aidlMod)); + ms.push_back(m); break; default: - status.modulations().resize(size); break; } } - hidlStatus.push_back(status); + if (ms.size() > 0) { + status.modulations(ms); + hidlStatus.push_back(status); + } break; } case TunerFrontendStatus::bers: { @@ -571,25 +604,31 @@ vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( } case TunerFrontendStatus::bandwidth: { auto aidlBand = s.get<TunerFrontendStatus::bandwidth>(); + FrontendBandwidth band; switch (mType) { case (int)FrontendType::ATSC3: - status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand)); + band.atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand)); + status.bandwidth(band); hidlStatus.push_back(status); break; case (int)FrontendType::DVBC: - status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand)); + band.dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand)); + status.bandwidth(band); hidlStatus.push_back(status); break; case (int)FrontendType::DVBT: - status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand)); + band.dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand)); + status.bandwidth(band); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBT: - status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand)); + band.isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand)); + status.bandwidth(band); hidlStatus.push_back(status); break; case (int)FrontendType::DTMB: - status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand)); + band.dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand)); + status.bandwidth(band); hidlStatus.push_back(status); break; default: @@ -599,17 +638,21 @@ vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( } case TunerFrontendStatus::interval: { auto aidlInter = s.get<TunerFrontendStatus::interval>(); + FrontendGuardInterval inter; switch (mType) { case (int)FrontendType::DVBT: - status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter)); + inter.dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter)); + status.interval(inter); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBT: - status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter)); + inter.isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter)); + status.interval(inter); hidlStatus.push_back(status); break; case (int)FrontendType::DTMB: - status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter)); + inter.dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter)); + status.interval(inter); hidlStatus.push_back(status); break; default: @@ -619,19 +662,21 @@ vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( } case TunerFrontendStatus::transmissionMode: { auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>(); + FrontendTransmissionMode trans; switch (mType) { case (int)FrontendType::DVBT: - status.transmissionMode().dvbt( - static_cast<FrontendDvbtTransmissionMode>(aidlTran)); + trans.dvbt(static_cast<FrontendDvbtTransmissionMode>(aidlTran)); + status.transmissionMode(trans); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBT: - status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran)); + trans.isdbt(static_cast<FrontendIsdbtMode>(aidlTran)); + status.transmissionMode(trans); hidlStatus.push_back(status); break; case (int)FrontendType::DTMB: - status.transmissionMode().dtmb( - static_cast<FrontendDtmbTransmissionMode>(aidlTran)); + trans.dtmb(static_cast<FrontendDtmbTransmissionMode>(aidlTran)); + status.transmissionMode(trans); hidlStatus.push_back(status); break; default: @@ -650,28 +695,30 @@ vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( break; } case TunerFrontendStatus::interleaving: { + vector<FrontendInterleaveMode> modes; for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) { - int size = status.interleaving().size(); - status.interleaving().resize(size + 1); + FrontendInterleaveMode mode; switch (mType) { case (int)FrontendType::DVBC: - status.interleaving()[size].dvbc( - static_cast<FrontendCableTimeInterleaveMode>(aidlInter)); + mode.dvbc(static_cast<FrontendCableTimeInterleaveMode>(aidlInter)); + modes.push_back(mode); break; case (int)FrontendType::ATSC3: - status.interleaving()[size].atsc3( - static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter)); + mode.atsc3(static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter)); + modes.push_back(mode); break; case (int)FrontendType::DTMB: - status.interleaving()[size].dtmb( - static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter)); + mode.dtmb(static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter)); + modes.push_back(mode); break; default: - status.interleaving().resize(size); break; } } - hidlStatus.push_back(status); + if (modes.size() > 0) { + status.interleaving(modes); + hidlStatus.push_back(status); + } break; } case TunerFrontendStatus::isdbtSegment: { @@ -690,17 +737,21 @@ vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( } case TunerFrontendStatus::rollOff: { auto aidlRoll = s.get<TunerFrontendStatus::rollOff>(); + FrontendRollOff roll; switch (mType) { case (int)FrontendType::DVBS: - status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll)); + roll.dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll)); + status.rollOff(roll); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBS: - status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll)); + roll.isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll)); + status.rollOff(roll); hidlStatus.push_back(status); break; case (int)FrontendType::ISDBS3: - status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll)); + roll.isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll)); + status.rollOff(roll); hidlStatus.push_back(status); break; default: diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h index 298b3974aeb9..f71616cb32b7 100644 --- a/media/jni/tuner/FrontendClient.h +++ b/media/jni/tuner/FrontendClient.h @@ -108,7 +108,7 @@ private: struct FrontendClient : public RefBase { public: - FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type); + FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type); ~FrontendClient(); /** @@ -180,6 +180,7 @@ public: shared_ptr<ITunerFrontend> getAidlFrontend(); + void setId(int id); int getId(); private: diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp index 7f954b561567..cf17ed6f4383 100644 --- a/media/jni/tuner/TunerClient.cpp +++ b/media/jni/tuner/TunerClient.cpp @@ -46,13 +46,12 @@ TunerClient::TunerClient() { // Connect with Tuner Service. ::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner")); mTunerService = ITunerService::fromBinder(binder); - // TODO: Remove after JNI migration is done. - mTunerService = NULL; if (mTunerService == NULL) { ALOGE("Failed to get tuner service"); } else { // TODO: b/178124017 update TRM in TunerService independently. mTunerService->updateTunerResources(); + mTunerService->getTunerHalVersion(&mTunerVersion); } } @@ -115,7 +114,7 @@ sp<FrontendClient> TunerClient::openFrontend(int frontendHandle) { if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { return NULL; } - return new FrontendClient(tunerFrontend, frontendHandle, aidlFrontendInfo.type); + return new FrontendClient(tunerFrontend, aidlFrontendInfo.type); } if (mTuner != NULL) { @@ -127,8 +126,10 @@ sp<FrontendClient> TunerClient::openFrontend(int frontendHandle) { if (res != Result::SUCCESS) { return NULL; } - sp<FrontendClient> frontendClient = new FrontendClient(NULL, id, (int)hidlInfo.type); + sp<FrontendClient> frontendClient = new FrontendClient( + NULL, (int)hidlInfo.type); frontendClient->setHidlFrontend(hidlFrontend); + frontendClient->setId(id); return frontendClient; } } @@ -358,7 +359,7 @@ void TunerClient::updateLnbResources() { sp<ITuner> TunerClient::getHidlTuner() { if (mTuner == NULL) { - mTunerVersion = 0; + mTunerVersion = TUNER_HAL_VERSION_UNKNOWN; mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService(); if (mTuner_1_1 == NULL) { @@ -367,11 +368,11 @@ sp<ITuner> TunerClient::getHidlTuner() { if (mTuner == NULL) { ALOGW("Failed to get tuner 1.0 service."); } else { - mTunerVersion = 1 << 16; + mTunerVersion = TUNER_HAL_VERSION_1_0; } } else { mTuner = static_cast<sp<ITuner>>(mTuner_1_1); - mTunerVersion = ((1 << 16) | 1); + mTunerVersion = TUNER_HAL_VERSION_1_1; } } return mTuner; diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h index 744bf2058766..9671cf787e3e 100644 --- a/media/jni/tuner/TunerClient.h +++ b/media/jni/tuner/TunerClient.h @@ -48,6 +48,10 @@ using namespace std; namespace android { +const static int TUNER_HAL_VERSION_UNKNOWN = 0; +const static int TUNER_HAL_VERSION_1_0 = 1 << 16; +const static int TUNER_HAL_VERSION_1_1 = (1 << 16) | 1; + typedef enum { FRONTEND, LNB, 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/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java index 09f285b445e9..14a7cfa568d8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java @@ -65,7 +65,7 @@ public class MobileMappings { return toIconKey(TelephonyManager.NETWORK_TYPE_LTE) + "_CA_Plus"; case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA: return toIconKey(TelephonyManager.NETWORK_TYPE_NR); - case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: + case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return toIconKey(TelephonyManager.NETWORK_TYPE_NR) + "_Plus"; default: return "unsupported"; @@ -180,7 +180,7 @@ public class MobileMappings { TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA), TelephonyIcons.NR_5G); networkToIconLookup.put(toDisplayIconKey( - TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE), + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED), TelephonyIcons.NR_5G_PLUS); networkToIconLookup.put(toIconKey( TelephonyManager.NETWORK_TYPE_NR), diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1432544da9bd..1566b761d23e 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -400,6 +400,7 @@ <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> + <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" /> <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" 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/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java index 0831e0ef7795..da079cf04403 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java @@ -103,5 +103,10 @@ public interface ToastPlugin extends Plugin { default Animator getOutAnimation() { return null; } + + /** + * Called on orientation changes. + */ + default void onOrientationChange(int orientation) { } } } 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/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/toast_background.xml b/packages/SystemUI/res/drawable/toast_background.xml new file mode 100644 index 000000000000..5c45e8346e3c --- /dev/null +++ b/packages/SystemUI/res/drawable/toast_background.xml @@ -0,0 +1,21 @@ +<?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"> + <solid android:color="#FFFFFFFF" /> + <corners android:radius="@dimen/toast_bg_radius" /> +</shape> 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/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index d6385ffbcc0c..859d9048cee3 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -31,6 +31,12 @@ android:layout_height="match_parent" android:visibility="gone" /> + <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" /> @@ -72,12 +78,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/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml new file mode 100644 index 000000000000..de4e062805fe --- /dev/null +++ b/packages/SystemUI/res/layout/text_toast.xml @@ -0,0 +1,51 @@ +<?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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxWidth="@dimen/toast_width" + android:orientation="horizontal" + android:background="@drawable/toast_background" + android:backgroundTint="?android:attr/colorBackground" + android:layout_marginEnd="16dp" + android:layout_marginStart="16dp" + android:gravity="center_vertical"> + + <!-- Icon should be 24x24, make slightly larger to allow for shadowing, adjust via padding --> + <ImageView + android:id="@+id/icon" + android:alpha="@dimen/toast_icon_alpha" + android:padding="11.5dp" + android:layout_width="@dimen/toast_icon_size" + android:layout_height="@dimen/toast_icon_size"/> + <TextView + android:id="@+id/text" + android:ellipsize="end" + android:maxLines="2" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:paddingStart="0dp" + android:paddingEnd="22dp" + android:textSize="@dimen/toast_text_size" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="@*android:string/config_headlineFontFamily" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> +</LinearLayout> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 51d7b8eff5fc..24c7655e5ae4 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -52,4 +52,6 @@ <!-- (footer_height -48dp)/2 --> <dimen name="controls_management_footer_top_margin">4dp</dimen> <dimen name="controls_management_favorites_top_margin">8dp</dimen> + + <dimen name="toast_y_offset">24dp</dimen> </resources> 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..8bd9de919bee 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 --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index bb04c3b35628..61962256f93d 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -107,7 +107,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 79471e6dacd2..594fbdf55d3c 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -743,9 +743,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 +802,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">32dp</dimen> <!-- Margin on the left side of the carrier text on Keyguard --> <dimen name="keyguard_carrier_text_margin">16dp</dimen> @@ -1324,8 +1321,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> @@ -1354,4 +1359,11 @@ <dimen name="rounded_slider_icon_size">24dp</dimen> <!-- rounded_slider_icon_size / 2 --> <dimen name="rounded_slider_icon_inset">12dp</dimen> + + <dimen name="toast_width">296dp</dimen> + <item name="toast_icon_alpha" format="float" type="dimen">1</item> + <dimen name="toast_text_size">14sp</dimen> + <dimen name="toast_y_offset">48dp</dimen> + <dimen name="toast_icon_size">48dp</dimen> + <dimen name="toast_bg_radius">28dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index ded8a2e66aea..3a9fec8c8748 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -23,6 +23,7 @@ <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,9 +35,8 @@ <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> + + <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..7c1c24bf280e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1112,6 +1112,9 @@ <!-- 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> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 7c72548a7252..85c470f8e706 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" /> @@ -764,6 +764,7 @@ <style name="TextAppearance.PrivacyDialog"> <item name="android:textSize">14sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> </style> <style name="UdfpsProgressBarStyle" diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java index 7f04f28198c0..72e4061829fa 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java @@ -32,8 +32,29 @@ public class PipSurfaceTransactionHelper { private final Matrix mTmpTransform = new Matrix(); private final float[] mTmpFloat9 = new float[9]; private final RectF mTmpSourceRectF = new RectF(); + private final RectF mTmpDestinationRectF = new RectF(); private final Rect mTmpDestinationRect = new Rect(); + public void scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, Rect destinationBounds) { + mTmpSourceRectF.set(sourceBounds); + mTmpDestinationRectF.set(destinationBounds); + mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); + tx.setMatrix(leash, mTmpTransform, mTmpFloat9) + .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); + } + + public void scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, Rect destinationBounds, + float degree, float positionX, float positionY) { + mTmpSourceRectF.set(sourceBounds); + mTmpDestinationRectF.set(destinationBounds); + mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); + mTmpTransform.postRotate(degree, 0, 0); + tx.setMatrix(leash, mTmpTransform, mTmpFloat9) + .setPosition(leash, positionX, positionY); + } + public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets) { mTmpSourceRectF.set(sourceBounds); 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/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/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index caaee5fd3f37..176562799838 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -49,7 +49,6 @@ import android.widget.TextView; import androidx.annotation.StyleRes; -import com.android.settingslib.Utils; import com.android.settingslib.graph.ThemedBatteryDrawable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.DarkIconDispatcher; @@ -105,11 +104,6 @@ public class BatteryMeterView extends LinearLayout implements private DualToneHandler mDualToneHandler; private int mUser; - /** - * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings. - */ - private boolean mUseWallpaperTextColors; - private int mNonAdaptedSingleToneColor; private int mNonAdaptedForegroundColor; private int mNonAdaptedBackgroundColor; @@ -242,31 +236,6 @@ public class BatteryMeterView extends LinearLayout implements mIsSubscribedForTunerUpdates = false; } - /** - * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll - * revert back to dark-mode-based/tinted colors. - * - * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all - * components - */ - public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) { - if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) { - return; - } - - mUseWallpaperTextColors = shouldUseWallpaperTextColor; - - if (mUseWallpaperTextColors) { - updateColors( - Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor), - Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary), - Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor)); - } else { - updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor, - mNonAdaptedSingleToneColor); - } - } - public void setColorsFromContext(Context context) { if (context == null) { return; @@ -476,13 +445,19 @@ public class BatteryMeterView extends LinearLayout implements mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity); mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity); - if (!mUseWallpaperTextColors) { - updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor, - mNonAdaptedSingleToneColor); - } + updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor, + mNonAdaptedSingleToneColor); } - private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) { + /** + * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events, + * if registered. + * + * @param foregroundColor + * @param backgroundColor + * @param singleToneColor + */ + public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) { mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor); mTextColor = singleToneColor; if (mBatteryPercentView != null) { 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/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt index f533cfb47076..db68d17461fa 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt @@ -28,11 +28,12 @@ import android.view.WindowManager import com.android.systemui.Interpolators import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher +import javax.inject.Inject /** * Show the controls space inside a dialog, as from the lock screen. */ -class ControlsDialog( +class ControlsDialog @Inject constructor( thisContext: Context, val broadcastDispatcher: BroadcastDispatcher ) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 84dd25963a15..f3726a37bb65 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -16,6 +16,11 @@ package com.android.systemui.dagger; +import android.content.Context; + +import com.android.systemui.SystemUIFactory; +import com.android.systemui.tv.TvWMComponent; +import com.android.systemui.wmshell.TvWMShellModule; import com.android.systemui.wmshell.WMShellModule; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellInit; @@ -34,7 +39,13 @@ import java.util.Optional; import dagger.Subcomponent; /** - * Dagger Subcomponent for WindowManager. + * Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported + * from the WM component into the SysUI component (in + * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies + * provided by its particular device/form-factor SystemUI implementation. + * + * ie. {@link WMComponent} includes {@link WMShellModule} + * and {@link TvWMComponent} includes {@link TvWMShellModule} */ @WMSingleton @Subcomponent(modules = {WMShellModule.class}) 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..9e5b225fbefc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -30,6 +30,7 @@ import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardViewController; 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 +62,7 @@ import dagger.Provides; /** * Dagger Module providing {@link StatusBar}. */ -@Module(subcomponents = {KeyguardStatusViewComponent.class}, +@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class}, includes = {FalsingModule.class}) public class KeyguardModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 4c96de232810..553b6d8679db 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -163,7 +163,8 @@ public class RingtonePlayer extends SystemUI { } @Override - public void setPlaybackProperties(IBinder token, float volume, boolean looping) { + public void setPlaybackProperties(IBinder token, float volume, boolean looping, + boolean hapticGeneratorEnabled) { Client client; synchronized (mClients) { client = mClients.get(token); @@ -171,6 +172,7 @@ public class RingtonePlayer extends SystemUI { if (client != null) { client.mRingtone.setVolume(volume); client.mRingtone.setLooping(looping); + client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled); } // else no client for token when setting playback properties but will be set at play() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index f56a890c54d4..782092161418 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -40,6 +40,7 @@ import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; @@ -92,9 +93,10 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSlider.Factory brightnessSliderFactory, - @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) { + @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, + FeatureFlags featureFlags) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, - metricsLogger, uiEventLogger, qsLogger, dumpManager); + metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); mQsSecurityFooter = qsSecurityFooter; mTunerService = tunerService; mQsCustomizerController = qsCustomizerController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index b02799f49660..9426e7122c1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -34,6 +34,8 @@ import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.util.Utils; import com.android.systemui.util.ViewController; import com.android.systemui.util.animation.DisappearParameters; @@ -63,6 +65,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final UiEventLogger mUiEventLogger; private final QSLogger mQSLogger; private final DumpManager mDumpManager; + private final FeatureFlags mFeatureFlags; protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); private int mLastOrientation; @@ -93,11 +96,18 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private boolean mUsingHorizontalLayout; - protected QSPanelControllerBase(T view, QSTileHost host, + protected QSPanelControllerBase( + T view, + QSTileHost host, QSCustomizerController qsCustomizerController, - @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost, - MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager) { + @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, + MediaHost mediaHost, + MetricsLogger metricsLogger, + UiEventLogger uiEventLogger, + QSLogger qsLogger, + DumpManager dumpManager, + FeatureFlags featureFlags + ) { super(view); mHost = host; mQsCustomizerController = qsCustomizerController; @@ -107,6 +117,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mUiEventLogger = uiEventLogger; mQSLogger = qsLogger; mDumpManager = dumpManager; + mFeatureFlags = featureFlags; } @Override @@ -334,9 +345,12 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr } boolean shouldUseHorizontalLayout() { + if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources())) { + return false; + } return mUsingMediaPlayer && mMediaHost.getVisible() - && getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; + && getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; } private void logTiles() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index a0db2000cb4d..383e932a6955 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -30,6 +30,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.statusbar.FeatureFlags; import java.util.ArrayList; import java.util.List; @@ -57,9 +58,11 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, @Named(QUICK_QS_PANEL) MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) { + DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, + FeatureFlags featureFlags + ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, - uiEventLogger, qsLogger, dumpManager); + uiEventLogger, qsLogger, dumpManager, featureFlags); mUseSideLabels = qsLabelsFlag; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 7776c12f587e..87252ff2b908 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -29,7 +29,6 @@ import android.media.AudioManager; import android.util.AttributeSet; import android.util.MathUtils; import android.util.Pair; -import android.view.ContextThemeWrapper; import android.view.DisplayCutout; import android.view.View; import android.view.ViewGroup; @@ -48,7 +47,6 @@ import androidx.lifecycle.LifecycleRegistry; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; -import com.android.systemui.DualToneHandler; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.privacy.OngoingPrivacyChip; @@ -75,7 +73,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn protected QuickQSPanel mHeaderQsPanel; private TouchAnimator mStatusIconsAlphaAnimator; private TouchAnimator mHeaderTextContainerAlphaAnimator; - private DualToneHandler mDualToneHandler; private View mSystemIconsView; private View mQuickQsStatusIcons; @@ -110,8 +107,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); - mDualToneHandler = new DualToneHandler( - new ContextThemeWrapper(context, R.style.QSHeaderTheme)); } @Override @@ -149,10 +144,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn } void onAttach(TintedIconManager iconManager) { - int colorForeground = Utils.getColorAttrDefaultColor(getContext(), - android.R.attr.colorForeground); - float intensity = getColorIntensity(colorForeground); - int fillColor = mDualToneHandler.getSingleColor(intensity); + int fillColor = Utils.getColorAttrDefaultColor(getContext(), + android.R.attr.textColorPrimary); // Set the correct tint for the status icons so they contrast iconManager.setTint(fillColor); @@ -271,14 +264,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); if (textColor != mTextColorPrimary) { + int textColorSecondary = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorSecondary); mTextColorPrimary = textColor; mClockView.setTextColor(textColor); - - float intensity = getColorIntensity(textColor); - int fillColor = mDualToneHandler.getSingleColor(intensity); - - Rect tintArea = new Rect(0, 0, 0, 0); - mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor); + mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary, + mTextColorPrimary); } updateStatusIconAlphaAnimator(); 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 7d8d86fe691e..eddcf8c1e9ae 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java @@ -28,9 +28,7 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; -import com.android.systemui.DualToneHandler; import com.android.systemui.R; -import com.android.systemui.qs.QuickStatusBarHeader; import java.util.Objects; @@ -40,9 +38,6 @@ public class QSCarrier extends LinearLayout { private TextView mCarrierText; private ImageView mMobileSignal; private ImageView mMobileRoaming; - private DualToneHandler mDualToneHandler; - private ColorStateList mColorForegroundStateList; - private float mColorForegroundIntensity; private CellSignalState mLastSignalState; public QSCarrier(Context context) { @@ -64,7 +59,6 @@ public class QSCarrier extends LinearLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mDualToneHandler = new DualToneHandler(getContext()); mMobileGroup = findViewById(R.id.mobile_combo); if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { mMobileRoaming = findViewById(R.id.mobile_roaming_large); @@ -74,11 +68,6 @@ public class QSCarrier extends LinearLayout { mMobileSignal = findViewById(R.id.mobile_signal); mCarrierText = findViewById(R.id.qs_carrier_text); mMobileSignal.setImageDrawable(new SignalDrawable(mContext)); - - int colorForeground = Utils.getColorAttrDefaultColor(mContext, - android.R.attr.colorForeground); - mColorForegroundStateList = ColorStateList.valueOf(colorForeground); - mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground); } /** @@ -92,8 +81,8 @@ public class QSCarrier extends LinearLayout { mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE); if (state.visible) { mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE); - ColorStateList colorStateList = ColorStateList.valueOf( - mDualToneHandler.getSingleColor(mColorForegroundIntensity)); + ColorStateList colorStateList = Utils.getColorAttr(mContext, + android.R.attr.textColorPrimary); mMobileRoaming.setImageTintList(colorStateList); mMobileSignal.setImageTintList(colorStateList); mMobileSignal.setImageLevel(state.mobileSignalIconId); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 11e6330d37f3..6983b38489f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -37,6 +37,7 @@ import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.DataSaverTile; +import com.android.systemui.qs.tiles.DeviceControlsTile; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.FlashlightTile; import com.android.systemui.qs.tiles.HotspotTile; @@ -89,6 +90,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider; private final Provider<CameraToggleTile> mCameraToggleTileProvider; private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider; + private final Provider<DeviceControlsTile> mDeviceControlsTileProvider; private final Lazy<QSHost> mQsHostLazy; private final Provider<CustomTile.Builder> mCustomTileBuilderProvider; @@ -123,7 +125,8 @@ public class QSFactoryImpl implements QSFactory { Provider<ScreenRecordTile> screenRecordTileProvider, Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider, Provider<CameraToggleTile> cameraToggleTileProvider, - Provider<MicrophoneToggleTile> microphoneToggleTileProvider) { + Provider<MicrophoneToggleTile> microphoneToggleTileProvider, + Provider<DeviceControlsTile> deviceControlsTileProvider) { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; @@ -153,6 +156,7 @@ public class QSFactoryImpl implements QSFactory { mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider; mCameraToggleTileProvider = cameraToggleTileProvider; mMicrophoneToggleTileProvider = microphoneToggleTileProvider; + mDeviceControlsTileProvider = deviceControlsTileProvider; } public QSTile createTile(String tileSpec) { @@ -212,6 +216,8 @@ public class QSFactoryImpl implements QSFactory { return mCameraToggleTileProvider.get(); case "mictoggle": return mMicrophoneToggleTileProvider.get(); + case "controls": + return mDeviceControlsTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt new file mode 100644 index 000000000000..6176a5702dcf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -0,0 +1,157 @@ +/* + * 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.qs.tiles + +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.service.quicksettings.Tile +import com.android.internal.logging.MetricsLogger +import com.android.systemui.R +import com.android.systemui.controls.ControlsServiceInfo +import com.android.systemui.controls.dagger.ControlsComponent +import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.util.settings.GlobalSettings +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject +import javax.inject.Provider + +class DeviceControlsTile @Inject constructor( + host: QSHost, + @Background backgroundLooper: Looper, + @Main mainHandler: Handler, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger, + private val controlsComponent: ControlsComponent, + private val featureFlags: FeatureFlags, + private val dialogProvider: Provider<ControlsDialog>, + globalSettings: GlobalSettings +) : QSTileImpl<QSTile.State>( + host, + backgroundLooper, + mainHandler, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger +) { + + companion object { + const val SETTINGS_FLAG = "controls_lockscreen" + } + + private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0 + private var hasControlsApps = AtomicBoolean(false) + private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS) + + private var controlsDialog: ControlsDialog? = null + private val icon = ResourceIcon.get(R.drawable.ic_device_light) + + private val listingCallback = object : ControlsListingController.ControlsListingCallback { + override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { + if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) { + refreshState() + } + } + } + + init { + controlsComponent.getControlsListingController().ifPresent { + it.observe(this, listingCallback) + } + } + + override fun isAvailable(): Boolean { + return featureFlags.isKeyguardLayoutEnabled && + controlsLockscreen && + controlsComponent.getControlsUiController().isPresent + } + + override fun newTileState(): QSTile.State { + return QSTile.State().also { + it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps` + } + } + + override fun handleDestroy() { + dismissDialog() + super.handleDestroy() + } + + private fun createDialog() { + if (controlsDialog?.isShowing != true) { + controlsDialog = dialogProvider.get() + } + } + + private fun dismissDialog() { + controlsDialog?.dismiss()?.also { + controlsDialog = null + } + } + + override fun handleClick() { + if (state.state != Tile.STATE_UNAVAILABLE) { + mUiHandler.post { + createDialog() + controlsDialog?.show(controlsComponent.getControlsUiController().get()) + } + } + } + + override fun handleUpdateState(state: QSTile.State, arg: Any?) { + state.label = tileLabel + state.secondaryLabel = "" + state.stateDescription = "" + state.contentDescription = state.label + state.icon = icon + if (hasControlsApps.get()) { + state.state = Tile.STATE_ACTIVE + if (controlsDialog == null) { + mUiHandler.post(this::createDialog) + } + } else { + state.state = Tile.STATE_UNAVAILABLE + dismissDialog() + } + } + + override fun getMetricsCategory(): Int { + return 0 + } + + override fun getLongClickIntent(): Intent { + return intent + } + + override fun getTileLabel(): CharSequence { + return mContext.getText(R.string.quick_controls_title) + } +}
\ No newline at end of file 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/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 543874325254..26781f4ccf09 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -277,12 +277,12 @@ public class ScreenMediaRecorder { */ void end() { mMediaRecorder.stop(); - mMediaProjection.stop(); mMediaRecorder.release(); - mMediaRecorder = null; - mMediaProjection = null; mInputSurface.release(); mVirtualDisplay.release(); + mMediaProjection.stop(); + mMediaRecorder = null; + mMediaProjection = null; stopInternalAudioRecording(); Log.d(TAG, "end recording"); 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/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/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 43bb34380f80..0bfc8e5d554b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -41,7 +41,7 @@ import android.service.vr.IVrStateCallbacks; import android.util.Log; import android.util.MathUtils; -import com.android.internal.BrightnessSynchronizer; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.RestrictedLockUtilsInternal; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 2dd85e9bb98d..7aa41e43be3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -67,11 +67,15 @@ public class FeatureFlags { return mFlagReader.isEnabled(R.bool.flag_brightness_slider); } - public boolean useNewLockscreenAnimations() { - return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations); - } - public boolean isPeopleTileEnabled() { return mFlagReader.isEnabled(R.bool.flag_conversations); } + + 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/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index c1feacaba440..2f0f90d318eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -11,10 +11,14 @@ import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffXfermode import android.graphics.RadialGradient import android.graphics.Shader +import android.os.SystemProperties import android.util.AttributeSet import android.view.View import com.android.systemui.Interpolators +val enableLightReveal = + SystemProperties.getBoolean("persist.sysui.show_new_screen_on_transitions", false) + /** * Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to * 100% of the view(s) underneath the scrim. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 85d8df8e6057..8c2fa3349e4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -29,7 +29,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.tuner.TunerService; @@ -55,7 +54,6 @@ public class DozeParameters implements TunerService.Tunable, private final AlwaysOnDisplayPolicy mAlwaysOnPolicy; private final Resources mResources; private final BatteryController mBatteryController; - private final FeatureFlags mFeatureFlags; private boolean mDozeAlwaysOn; private boolean mControlScreenOffAnimation; @@ -67,8 +65,7 @@ public class DozeParameters implements TunerService.Tunable, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, PowerManager powerManager, BatteryController batteryController, - TunerService tunerService, - FeatureFlags featureFlags) { + TunerService tunerService) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; @@ -77,7 +74,6 @@ public class DozeParameters implements TunerService.Tunable, mControlScreenOffAnimation = !getDisplayNeedsBlanking(); mPowerManager = powerManager; mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation); - mFeatureFlags = featureFlags; tunerService.addTunable( this, @@ -204,7 +200,8 @@ public class DozeParameters implements TunerService.Tunable, * then abruptly showing AOD. */ public boolean shouldControlUnlockedScreenOff() { - return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations(); + return getAlwaysOn() && SystemProperties.getBoolean( + "persist.sysui.show_new_screen_on_transitions", false); } private boolean getBoolean(String propName, int resId) { 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..e0df4f8bfea9 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,6 +56,12 @@ public class KeyguardClockPositionAlgorithm { private int mKeyguardStatusHeight; /** + * Height of {@link KeyguardUserSwitcherListView} when it + * is closed and only the current user's icon is visible. + */ + private int mKeyguardUserSwitcherHeight; + + /** * Preferred Y position of clock. */ private int mClockPreferredY; @@ -173,17 +180,20 @@ public class KeyguardClockPositionAlgorithm { * Sets up algorithm values. */ public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight, - float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY, - boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount, - boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon, - float qsExpansion, int cutoutTopInset) { + float panelExpansion, int parentHeight, int keyguardStatusHeight, + int keyguardUserSwitcherHeight, int clockPreferredY, 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) + + keyguardUserSwitcherHeight; mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; mPanelExpansion = panelExpansion; mHeight = parentHeight; mKeyguardStatusHeight = keyguardStatusHeight; + mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight; mClockPreferredY = clockPreferredY; mHasCustomClock = hasCustomClock; mHasVisibleNotifs = hasVisibleNotifs; @@ -246,7 +256,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 + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT - mClockNotificationsMargin - mNotificationStackHeight / 2; if (y < mMinTopMargin) { y = mMinTopMargin; 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..d9cb9ce21330 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]; 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 e0ef3b6483a5..3b09eda4003e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -48,6 +48,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 +58,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; @@ -76,6 +78,7 @@ import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; 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; @@ -95,7 +98,6 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; @@ -132,8 +134,10 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUi import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; 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; import java.io.FileDescriptor; @@ -283,6 +287,22 @@ public class NotificationPanelViewController extends PanelViewController { } }; + final KeyguardUserSwitcherController.KeyguardUserSwitcherListener + mKeyguardUserSwitcherListener = + new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() { + @Override + public void onKeyguardUserSwitcherChanged(boolean open) { + if (mKeyguardUserSwitcherController != null + && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) { + return; + } + + updateUserSwitcherVisibility(open + && mKeyguardStateController.isShowing() + && !mKeyguardStateController.isKeyguardFadingAway()); + } + }; + private final LayoutInflater mLayoutInflater; private final PowerManager mPowerManager; private final AccessibilityManager mAccessibilityManager; @@ -295,7 +315,7 @@ public class NotificationPanelViewController extends PanelViewController { private final MediaHierarchyManager mMediaHierarchyManager; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; - private final QSDetailDisplayer mQSDetailDisplayer; + private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; private final ControlsComponent mControlsComponent; @@ -307,7 +327,7 @@ public class NotificationPanelViewController extends PanelViewController { private int mMaxAllowedKeyguardNotifications; private KeyguardAffordanceHelper mAffordanceHelper; - private KeyguardUserSwitcher mKeyguardUserSwitcher; + private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; private ViewGroup mBigClockContainer; private QS mQs; @@ -332,6 +352,7 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mQsExpandedWhenExpandingStarted; private boolean mQsFullyExpanded; private boolean mKeyguardShowing; + private boolean mKeyguardUserSwitcherEnabled; private boolean mDozing; private boolean mDozingOnDown; private int mBarState; @@ -464,6 +485,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; @@ -556,11 +578,12 @@ public class NotificationPanelViewController extends PanelViewController { StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, + KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, AuthController authController, - QSDetailDisplayer qsDetailDisplayer, ScrimController scrimController, + UserManager userManager, MediaDataManager mediaDataManager, AmbientState ambientState, FeatureFlags featureFlags, @@ -581,8 +604,10 @@ public class NotificationPanelViewController extends PanelViewController { mGroupManager = groupManager; mNotificationIconAreaController = notificationIconAreaController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; - mQSDetailDisplayer = qsDetailDisplayer; mFeatureFlags = featureFlags; + mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory; + mKeyguardUserSwitcherEnabled = mResources.getBoolean( + com.android.internal.R.bool.config_keyguardUserSwitcher); mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; @@ -598,6 +623,7 @@ public class NotificationPanelViewController extends PanelViewController { mDozeParameters = dozeParameters; mBiometricUnlockController = biometricUnlockController; mScrimController = scrimController; + mUserManager = userManager; mMediaDataManager = mediaDataManager; mControlsComponent = controlsComponent; pulseExpansionHandler.setPulseExpandAbortListener(() -> { @@ -660,9 +686,17 @@ 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)); + + KeyguardUserSwitcherView keyguardUserSwitcherView = null; + + if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) { + ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub); + keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate(); + } + + updateViewControllers(mView.findViewById(R.id.keyguard_status_view), + keyguardUserSwitcherView); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); NotificationStackScrollLayout stackScrollLayout = mView.findViewById( R.id.notification_stack_scroller); @@ -735,7 +769,8 @@ public class NotificationPanelViewController extends PanelViewController { R.dimen.heads_up_status_bar_padding); } - private void updateViewControllers(KeyguardStatusView keyguardStatusView) { + private void updateViewControllers(KeyguardStatusView keyguardStatusView, + KeyguardUserSwitcherView keyguardUserSwitcherView) { // Re-associate the KeyguardStatusViewController KeyguardStatusViewComponent statusViewComponent = mKeyguardStatusViewComponentFactory.build(keyguardStatusView); @@ -746,6 +781,28 @@ 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(); + } + + // Re-associate the KeyguardUserSwitcherController + if (keyguardUserSwitcherView != null) { + KeyguardUserSwitcherComponent userSwitcherComponent = + mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView); + + mKeyguardUserSwitcherController = + userSwitcherComponent.getKeyguardUserSwitcherController(); + mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener); + mKeyguardUserSwitcherController.init(); + mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true); + } else { + mKeyguardUserSwitcherController = null; + mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false); + } } /** @@ -783,18 +840,13 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setLayoutParams(lp); } - if (shouldUseSplitNotificationShade()) { + if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) { // In order to change the constraints at runtime, all children of the Constraint Layout // must have ids. ensureAllViewsHaveIds(mNotificationContainerParent); } } - private boolean shouldUseSplitNotificationShade() { - return mFeatureFlags.isTwoColumnNotificationShadeEnabled() - && mResources.getBoolean(R.bool.config_use_split_notification_shade); - } - private static void ensureAllViewsHaveIds(ViewGroup parentView) { for (int i = 0; i < parentView.getChildCount(); i++) { View childView = parentView.getChildAt(i); @@ -805,6 +857,7 @@ public class NotificationPanelViewController extends PanelViewController { } 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); @@ -813,8 +866,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 showUserSwitcher = + mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled(); + KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById( + R.id.keyguard_user_switcher_view); + if (keyguardUserSwitcherView != null) { + index = mView.indexOfChild(keyguardUserSwitcherView); + mView.removeView(keyguardUserSwitcherView); + if (showUserSwitcher) { + keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate( + R.layout.keyguard_user_switcher, mView, false); + mView.addView(keyguardUserSwitcherView, index); + } + } else if (showUserSwitcher) { + // It's possible the user switcher was never inflated if the configuration changed + ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub); + keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate(); + } + mBigClockContainer.removeAllViews(); - updateViewControllers(keyguardStatusView); + updateViewControllers(keyguardStatusView, keyguardUserSwitcherView); // Update keyguard bottom area index = mView.indexOfChild(mKeyguardBottomArea); @@ -838,6 +910,13 @@ public class NotificationPanelViewController extends PanelViewController { false, false, mBarState); + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility( + mBarState, + false, + false, + mBarState); + } setKeyguardBottomAreaVisibility(mBarState, false); } @@ -942,6 +1021,8 @@ public class NotificationPanelViewController extends PanelViewController { ? mKeyguardStatusViewController.getHeight() : (int) (mKeyguardStatusViewController.getHeight() - mShelfHeight / 2.0f - mDarkIconSize / 2.0f), + mKeyguardUserSwitcherController == null + ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(), clockPreferredY, hasCustomClock(), hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount, bypassEnabled, getUnlockedStackScrollerPadding(), @@ -952,6 +1033,13 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockY, mClockPositionResult.clockScale, animateClock); + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.updatePosition( + mClockPositionResult.clockX, + mClockPositionResult.clockY + - mKeyguardUserSwitcherController.getUserIconHeight(), + animateClock); + } updateNotificationTranslucency(); updateClock(); stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded; @@ -1092,6 +1180,9 @@ public class NotificationPanelViewController extends PanelViewController { private void updateClock() { mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha); + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha); + } } public void animateToFullShade(long delay) { @@ -1773,8 +1864,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); @@ -2608,10 +2700,6 @@ public class NotificationPanelViewController extends PanelViewController { } } - public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { - mKeyguardUserSwitcher = keyguardUserSwitcher; - } - public void onScreenTurningOn() { mKeyguardStatusViewController.dozeTimeTick(); } @@ -3078,6 +3166,13 @@ public class NotificationPanelViewController extends PanelViewController { true /* keyguardFadingAway */, false /* goingToFullShade */, mBarState); + if (mKeyguardUserSwitcherController != null) { + mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility( + mBarState, + true /* keyguardFadingAway */, + false /* goingToFullShade */, + mBarState); + } } /** @@ -3320,6 +3415,44 @@ 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) { + 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) { @@ -3620,6 +3753,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; @@ -3631,11 +3765,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..e63902f26a78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -35,6 +35,7 @@ import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTE import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; +import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; @@ -89,7 +90,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; @@ -180,7 +180,6 @@ import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -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; @@ -442,7 +440,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final KeyguardViewMediator mKeyguardViewMediator; protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final BrightnessSlider.Factory mBrightnessSliderFactory; - private final FeatureFlags mFeatureFlags; private final List<ExpansionChangedListener> mExpansionChangedListeners; @@ -623,7 +620,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); @@ -766,8 +762,7 @@ public class StatusBar extends SystemUI implements DemoMode, Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, StatusBarTouchableRegionManager statusBarTouchableRegionManager, NotificationIconAreaController notificationIconAreaController, - BrightnessSlider.Factory brightnessSliderFactory, - FeatureFlags featureFlags) { + BrightnessSlider.Factory brightnessSliderFactory) { super(context); mNotificationsController = notificationsController; mLightBarController = lightBarController; @@ -845,7 +840,6 @@ public class StatusBar extends SystemUI implements DemoMode, mDemoModeController = demoModeController; mNotificationIconAreaController = notificationIconAreaController; mBrightnessSliderFactory = brightnessSliderFactory; - mFeatureFlags = featureFlags; mExpansionChangedListeners = new ArrayList<>(); @@ -1187,11 +1181,9 @@ public class StatusBar extends SystemUI implements DemoMode, mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); - if (mFeatureFlags.useNewLockscreenAnimations() && mDozeParameters.getAlwaysOn()) { + if (getEnableLightReveal()) { mLightRevealScrim.setVisibility(View.VISIBLE); mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); - } else { - mLightRevealScrim.setVisibility(View.GONE); } mNotificationPanelViewController.initDependencies( @@ -1212,9 +1204,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 +1430,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 +1463,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 +3245,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 +3544,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 +3601,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(); @@ -3647,7 +3614,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDozeAmountChanged(float linear, float eased) { - if (mFeatureFlags.useNewLockscreenAnimations()) { + if (getEnableLightReveal()) { mLightRevealScrim.setRevealAmount(1f - linear); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index b572c57590ae..9e9533d0e199 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -47,7 +47,6 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; @@ -201,8 +200,7 @@ public interface StatusBarPhoneModule { DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager, NotificationIconAreaController notificationIconAreaController, - BrightnessSlider.Factory brightnessSliderFactory, - FeatureFlags featureFlags) { + BrightnessSlider.Factory brightnessSliderFactory) { return new StatusBar( context, notificationsController, @@ -281,7 +279,6 @@ public interface StatusBarPhoneModule { notificationShadeDepthController, statusBarTouchableRegionManager, notificationIconAreaController, - brightnessSliderFactory, - featureFlags); + brightnessSliderFactory); } } 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/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 68d74ef760b4..1838391ce2ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -423,7 +423,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); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index e357577f88c8..3f0141416f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -15,12 +15,15 @@ */ 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.os.SystemProperties; +import android.content.om.OverlayManagerTransaction; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -50,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"; @@ -65,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 @@ -117,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. */ @@ -162,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()); @@ -229,55 +166,64 @@ public class ThemeOverlayApplier implements Dumpable { final List<OverlayInfo> overlays = new ArrayList<>(); targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM))); - final Map<String, String> overlaysToDisable = overlays.stream() + final List<Pair<String, String>> overlaysToDisable = overlays.stream() .filter(o -> mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) .filter(o -> overlayCategoriesToDisable.contains(o.category)) .filter(o -> o.isEnabled()) - .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName)); + .map(o -> new Pair<>(o.category, o.packageName)) + .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)) { - setEnabled(categoryToPackage.get(category), category, userHandles, true); - } else if (overlaysToDisable.containsKey(category)) { - setEnabled(overlaysToDisable.get(category), category, userHandles, false); + OverlayIdentifier overlayInfo = categoryToPackage.get(category); + setEnabled(transaction, overlayInfo, category, userHandles, true); } } + for (Pair<String, String> packageToDisable : overlaysToDisable) { + OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); + setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false); + } + + mExecutor.execute(() -> { + mOverlayManager.commit(transaction.build()); + }); } - private void setEnabled( - String packageName, String category, Set<UserHandle> handles, boolean enabled) { + @VisibleForTesting + protected OverlayManagerTransaction.Builder getTransactionBuilder() { + return new OverlayManagerTransaction.Builder(); + } + + private void setEnabled(OverlayManagerTransaction.Builder transaction, + OverlayIdentifier identifier, String category, Set<UserHandle> handles, + boolean enabled) { + if (DEBUG) { + Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: " + + category + ": " + enabled); + } for (UserHandle userHandle : handles) { - setEnabledAsync(packageName, userHandle, enabled); + transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); } if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { - setEnabledAsync(packageName, UserHandle.SYSTEM, enabled); + transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier()); } } - private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) { - mExecutor.execute(() -> { - if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled)); - try { - if (enabled) { - mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle); - } else { - mOverlayManager.setEnabled(pkg, false, userHandle); - } - } catch (SecurityException | IllegalStateException e) { - Log.e(TAG, - String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e); - } - }); - } - /** * @inherit */ @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/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java index e9fcf1aa9598..365cd2a5d20b 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java @@ -20,10 +20,21 @@ import android.animation.Animator; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.util.Log; +import android.view.LayoutInflater; import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; import android.widget.ToastPresenter; import com.android.internal.R; +import com.android.launcher3.icons.IconFactory; import com.android.systemui.plugins.ToastPlugin; /** @@ -35,23 +46,43 @@ public class SystemUIToast implements ToastPlugin.Toast { final CharSequence mText; final ToastPlugin.Toast mPluginToast; - final int mDefaultGravity; - final int mDefaultY; + private final String mPackageName; + private final int mUserId; + private final LayoutInflater mLayoutInflater; + private final boolean mToastStyleEnabled; + final int mDefaultX = 0; final int mDefaultHorizontalMargin = 0; final int mDefaultVerticalMargin = 0; - SystemUIToast(Context context, CharSequence text) { - this(context, text, null); + private int mDefaultY; + private int mDefaultGravity; + + @NonNull private final View mToastView; + @Nullable private final Animator mInAnimator; + @Nullable private final Animator mOutAnimator; + + SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, + String packageName, int userId, boolean toastStyleEnabled, int orientation) { + this(layoutInflater, context, text, null, packageName, userId, + toastStyleEnabled, orientation); } - SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) { + SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, + ToastPlugin.Toast pluginToast, String packageName, int userId, + boolean toastStyleEnabled, int orientation) { + mToastStyleEnabled = toastStyleEnabled; + mLayoutInflater = layoutInflater; mContext = context; mText = text; mPluginToast = pluginToast; + mPackageName = packageName; + mUserId = userId; + mToastView = inflateToastView(); + mInAnimator = createInAnimator(); + mOutAnimator = createOutAnimator(); - mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity); - mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); + onOrientationChange(orientation); } @Override @@ -102,28 +133,19 @@ public class SystemUIToast implements ToastPlugin.Toast { @Override @NonNull public View getView() { - if (isPluginToast() && mPluginToast.getView() != null) { - return mPluginToast.getView(); - } - return ToastPresenter.getTextToastView(mContext, mText); + return mToastView; } @Override @Nullable public Animator getInAnimation() { - if (isPluginToast() && mPluginToast.getInAnimation() != null) { - return mPluginToast.getInAnimation(); - } - return null; + return mInAnimator; } @Override @Nullable public Animator getOutAnimation() { - if (isPluginToast() && mPluginToast.getOutAnimation() != null) { - return mPluginToast.getOutAnimation(); - } - return null; + return mOutAnimator; } /** @@ -136,4 +158,80 @@ public class SystemUIToast implements ToastPlugin.Toast { private boolean isPluginToast() { return mPluginToast != null; } + + private View inflateToastView() { + if (isPluginToast() && mPluginToast.getView() != null) { + return mPluginToast.getView(); + } + + View toastView; + if (mToastStyleEnabled) { + toastView = mLayoutInflater.inflate( + com.android.systemui.R.layout.text_toast, null); + ((TextView) toastView.findViewById(com.android.systemui.R.id.text)).setText(mText); + + ((ImageView) toastView.findViewById(com.android.systemui.R.id.icon)) + .setImageDrawable(getBadgedIcon(mContext, mPackageName, mUserId)); + } else { + toastView = ToastPresenter.getTextToastView(mContext, mText); + } + + return toastView; + } + + /** + * Called on orientation changes to update parameters associated with the toast placement. + */ + public void onOrientationChange(int orientation) { + if (mPluginToast != null) { + mPluginToast.onOrientationChange(orientation); + } + + mDefaultY = mContext.getResources().getDimensionPixelSize( + mToastStyleEnabled + ? com.android.systemui.R.dimen.toast_y_offset + : R.dimen.toast_y_offset); + mDefaultGravity = + mContext.getResources().getInteger(R.integer.config_toastDefaultGravity); + } + + private Animator createInAnimator() { + if (isPluginToast() && mPluginToast.getInAnimation() != null) { + return mPluginToast.getInAnimation(); + } + + return mToastStyleEnabled + ? ToastDefaultAnimation.Companion.toastIn(getView()) + : null; + } + + private Animator createOutAnimator() { + if (isPluginToast() && mPluginToast.getOutAnimation() != null) { + return mPluginToast.getOutAnimation(); + } + return mToastStyleEnabled + ? ToastDefaultAnimation.Companion.toastOut(getView()) + : null; + } + + /** + * Get badged app icon if necessary, similar as used in the Settings UI. + * @return The icon to use + */ + public static Drawable getBadgedIcon(@NonNull Context context, String packageName, + int userId) { + final PackageManager packageManager = context.getPackageManager(); + try { + final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( + packageName, PackageManager.GET_META_DATA, userId); + UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); + IconFactory iconFactory = IconFactory.obtain(context); + Bitmap iconBmp = iconFactory.createBadgedIconBitmap( + appInfo.loadUnbadgedIcon(packageManager), user, false).icon; + return new BitmapDrawable(context.getResources(), iconBmp); + } catch (PackageManager.NameNotFoundException e) { + Log.e("SystemUIToast", "could not load icon for package=" + packageName + " e=" + e); + return null; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt new file mode 100644 index 000000000000..603d69057e5c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt @@ -0,0 +1,105 @@ +/* + * 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.toast + +import android.animation.ObjectAnimator +import android.view.View +import android.view.animation.LinearInterpolator +import android.view.animation.PathInterpolator +import android.animation.AnimatorSet + +class ToastDefaultAnimation { + /** + * sum of the in and out animation durations cannot exceed + * [com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER] to prevent the toast + * window from being removed before animations are completed + */ + companion object { + // total duration shouldn't exceed NotificationManagerService's delay for "in" animation + fun toastIn(view: View): AnimatorSet? { + val icon: View? = view.findViewById(com.android.systemui.R.id.icon) + val text: View? = view.findViewById(com.android.systemui.R.id.text) + if (icon == null || text == null) { + return null + } + val linearInterp = LinearInterpolator() + val scaleInterp = PathInterpolator(0f, 0f, 0f, 1f) + val sX = ObjectAnimator.ofFloat(view, "scaleX", 0.9f, 1f).apply { + interpolator = scaleInterp + duration = 333 + } + val sY = ObjectAnimator.ofFloat(view, "scaleY", 0.9f, 1f).apply { + interpolator = scaleInterp + duration = 333 + } + val vA = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply { + interpolator = linearInterp + duration = 66 + } + text.alpha = 0f // Set now otherwise won't apply until start delay + val tA = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f).apply { + interpolator = linearInterp + duration = 283 + startDelay = 50 + } + icon.alpha = 0f // Set now otherwise won't apply until start delay + val iA = ObjectAnimator.ofFloat(icon, "alpha", 0f, 1f).apply { + interpolator = linearInterp + duration = 283 + startDelay = 50 + } + return AnimatorSet().apply { + playTogether(sX, sY, vA, tA, iA) + } + } + + fun toastOut(view: View): AnimatorSet? { + // total duration shouldn't exceed NotificationManagerService's delay for "out" anim + val icon: View? = view.findViewById(com.android.systemui.R.id.icon) + val text: View? = view.findViewById(com.android.systemui.R.id.text) + if (icon == null || text == null) { + return null + } + val linearInterp = LinearInterpolator() + val scaleInterp = PathInterpolator(0.3f, 0f, 1f, 1f) + val sX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.9f).apply { + interpolator = scaleInterp + duration = 250 + } + val sY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.9f).apply { + interpolator = scaleInterp + duration = 250 + } + val vA = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply { + interpolator = linearInterp + duration = 100 + startDelay = 150 + } + val tA = ObjectAnimator.ofFloat(text, "alpha", 1f, 0f).apply { + interpolator = linearInterp + duration = 166 + } + val iA = ObjectAnimator.ofFloat(icon, "alpha", 1f, 0f).apply { + interpolator = linearInterp + duration = 166 + } + return AnimatorSet().apply { + playTogether(sX, sY, vA, tA, iA) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java index d8cb61c6b349..8b782d4b7923 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java @@ -17,6 +17,7 @@ package com.android.systemui.toast; import android.content.Context; +import android.view.LayoutInflater; import androidx.annotation.NonNull; @@ -26,6 +27,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.ToastPlugin; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.FeatureFlags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -40,10 +42,18 @@ import javax.inject.Inject; public class ToastFactory implements Dumpable { // only one ToastPlugin can be connected at a time. private ToastPlugin mPlugin; + private final LayoutInflater mLayoutInflater; + private final boolean mToastStyleEnabled; @Inject - public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) { + public ToastFactory( + LayoutInflater layoutInflater, + PluginManager pluginManager, + DumpManager dumpManager, + FeatureFlags featureFlags) { + mLayoutInflater = layoutInflater; dumpManager.registerDumpable("ToastFactory", this); + mToastStyleEnabled = featureFlags.isToastStyleEnabled(); pluginManager.addPluginListener( new PluginListener<ToastPlugin>() { @Override @@ -64,11 +74,13 @@ public class ToastFactory implements Dumpable { * Create a toast to be shown by ToastUI. */ public SystemUIToast createToast(Context context, CharSequence text, String packageName, - int userId) { + int userId, int orientation) { if (isPluginAvailable()) { - return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId)); + return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text, + packageName, userId), packageName, userId, mToastStyleEnabled, orientation); } - return new SystemUIToast(context, text); + return new SystemUIToast(mLayoutInflater, context, text, packageName, userId, + mToastStyleEnabled, orientation); } private boolean isPluginAvailable() { @@ -79,5 +91,6 @@ public class ToastFactory implements Dumpable { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("ToastFactory:"); pw.println(" mAttachedPlugin=" + mPlugin); + pw.println(" mToastStyleEnabled=" + mToastStyleEnabled); } } diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt index 78173cf62a93..51541bd3032e 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt @@ -49,6 +49,15 @@ class ToastLogger @Inject constructor( }) } + fun logOrientationChange(text: String, isPortrait: Boolean) { + log(DEBUG, { + str1 = text + bool1 = isPortrait + }, { + "Orientation change for toast. msg=\'$str1\' isPortrait=$bool1" + }) + } + private inline fun log( logLevel: LogLevel, initializer: LogMessage.() -> Unit, diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index 409d1361223c..92ea1d0e5fbd 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -16,27 +16,28 @@ package com.android.systemui.toast; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; + import android.animation.Animator; import android.annotation.MainThread; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.INotificationManager; import android.app.ITransientNotificationCallback; import android.content.Context; +import android.content.res.Configuration; import android.os.IBinder; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Log; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; -import android.widget.Toast; import android.widget.ToastPresenter; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.Objects; @@ -58,18 +59,19 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { private final IAccessibilityManager mIAccessibilityManager; private final AccessibilityManager mAccessibilityManager; private final ToastFactory mToastFactory; - private final DelayableExecutor mMainExecutor; private final ToastLogger mToastLogger; private SystemUIToast mToast; @Nullable private ToastPresenter mPresenter; @Nullable private ITransientNotificationCallback mCallback; + private ToastOutAnimatorListener mToastOutAnimatorListener; + + private int mOrientation = ORIENTATION_PORTRAIT; @Inject public ToastUI( Context context, CommandQueue commandQueue, ToastFactory toastFactory, - @Main DelayableExecutor mainExecutor, ToastLogger toastLogger) { this(context, commandQueue, INotificationManager.Stub.asInterface( @@ -77,21 +79,19 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { IAccessibilityManager.Stub.asInterface( ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)), toastFactory, - mainExecutor, toastLogger); } @VisibleForTesting ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager, @Nullable IAccessibilityManager accessibilityManager, - ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger + ToastFactory toastFactory, ToastLogger toastLogger ) { super(context); mCommandQueue = commandQueue; mNotificationManager = notificationManager; mIAccessibilityManager = accessibilityManager; mToastFactory = toastFactory; - mMainExecutor = mainExecutor; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mToastLogger = toastLogger; } @@ -105,36 +105,38 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { @MainThread public void showToast(int uid, String packageName, IBinder token, CharSequence text, IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { - if (mPresenter != null) { - hideCurrentToast(); - } - UserHandle userHandle = UserHandle.getUserHandleForUid(uid); - Context context = mContext.createContextAsUser(userHandle, 0); - mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier()); + Runnable showToastRunnable = () -> { + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + Context context = mContext.createContextAsUser(userHandle, 0); + mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName, + userHandle.getIdentifier(), mOrientation); - if (mToast.hasCustomAnimation()) { if (mToast.getInAnimation() != null) { mToast.getInAnimation().start(); } - final Animator hideAnimator = mToast.getOutAnimation(); - if (hideAnimator != null) { - final long durationMillis = duration == Toast.LENGTH_LONG - ? TOAST_LONG_TIME : TOAST_SHORT_TIME; - final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis( - (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT); - mMainExecutor.executeDelayed(() -> hideAnimator.start(), - updatedDuration - hideAnimator.getTotalDuration()); - } + + mCallback = callback; + mPresenter = new ToastPresenter(context, mIAccessibilityManager, + mNotificationManager, packageName); + // Set as trusted overlay so touches can pass through toasts + mPresenter.getLayoutParams().setTrustedOverlay(); + mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString()); + mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(), + mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(), + mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation()); + }; + + if (mToastOutAnimatorListener != null) { + // if we're currently animating out a toast, show new toast after prev toast is hidden + mToastOutAnimatorListener.setShowNextToastRunnable(showToastRunnable); + } else if (mPresenter != null) { + // if there's a toast already showing that we haven't tried hiding yet, hide it and + // then show the next toast after its hidden animation is done + hideCurrentToast(showToastRunnable); + } else { + // else, show this next toast immediately + showToastRunnable.run(); } - mCallback = callback; - mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager, - packageName); - // Set as trusted overlay so touches can pass through toasts - mPresenter.getLayoutParams().setTrustedOverlay(); - mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString()); - mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(), - mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(), - mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation()); } @Override @@ -146,12 +148,88 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { return; } mToastLogger.logOnHideToast(packageName, token.toString()); - hideCurrentToast(); + hideCurrentToast(null); } @MainThread - private void hideCurrentToast() { - mPresenter.hide(mCallback); + private void hideCurrentToast(Runnable runnable) { + if (mToast.getOutAnimation() != null) { + Animator animator = mToast.getOutAnimation(); + mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback, + runnable); + animator.addListener(mToastOutAnimatorListener); + animator.start(); + } else { + mPresenter.hide(mCallback); + if (runnable != null) { + runnable.run(); + } + } + mToast = null; mPresenter = null; + mCallback = null; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + if (newConfig.orientation != mOrientation) { + mOrientation = newConfig.orientation; + if (mToast != null) { + mToastLogger.logOrientationChange(mToast.mText.toString(), + mOrientation == ORIENTATION_PORTRAIT); + mToast.onOrientationChange(mOrientation); + mPresenter.updateLayoutParams( + mToast.getXOffset(), + mToast.getYOffset(), + mToast.getHorizontalMargin(), + mToast.getVerticalMargin(), + mToast.getGravity()); + } + } + } + + /** + * Once the out animation for a toast is finished, start showing the next toast. + */ + class ToastOutAnimatorListener implements Animator.AnimatorListener { + final ToastPresenter mPrevPresenter; + final ITransientNotificationCallback mPrevCallback; + @Nullable Runnable mShowNextToastRunnable; + + ToastOutAnimatorListener( + @NonNull ToastPresenter presenter, + @NonNull ITransientNotificationCallback callback, + @Nullable Runnable runnable) { + mPrevPresenter = presenter; + mPrevCallback = callback; + mShowNextToastRunnable = runnable; + } + + void setShowNextToastRunnable(Runnable runnable) { + mShowNextToastRunnable = runnable; + } + + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mPrevPresenter.hide(mPrevCallback); + if (mShowNextToastRunnable != null) { + mShowNextToastRunnable.run(); + } + mToastOutAnimatorListener = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + onAnimationEnd(animation); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 72f1f22c0ba1..fd3641cfdaa0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -20,12 +20,15 @@ import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.provider.Settings; import android.text.TextUtils; import android.view.View; +import com.android.systemui.R; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import java.util.HashSet; import java.util.List; @@ -163,4 +166,15 @@ public class Utils { } return apps; } + + /** + * Returns true if the device should use the split notification shade, based on feature flags, + * orientation and screen width. + */ + public static boolean shouldUseSplitNotificationShade(FeatureFlags featureFlags, + Resources resources) { + return featureFlags.isTwoColumnNotificationShadeEnabled() + && resources.getBoolean(R.bool.config_use_split_notification_shade); + } + } 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/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java index 0795d89eb0bc..ff2881953342 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java @@ -19,6 +19,7 @@ package com.android.systemui.wmshell; import android.content.Context; import android.os.Handler; +import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -28,6 +29,7 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; 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.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; @@ -49,7 +51,7 @@ import dagger.Module; import dagger.Provides; /** - * Dagger module for TV Pip. + * Provides TV specific dependencies for Pip. */ @Module(includes = {WMShellBaseModule.class}) public abstract class TvPipModule { @@ -143,7 +145,8 @@ public abstract class TvPipModule { PipAnimationController pipAnimationController, PipTransitionController pipTransitionController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController, + Optional<LegacySplitScreenController> splitScreenOptional, + DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm, diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java index f23367b4d65b..141b9f7d410d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java @@ -20,6 +20,7 @@ import android.animation.AnimationHandler; import android.content.Context; import android.view.IWindowManager; +import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -39,11 +40,20 @@ import dagger.Module; import dagger.Provides; /** - * Provides dependencies from {@link com.android.wm.shell} which could be customized among different - * branches of SystemUI. + * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only + * accessible from components within the WM subcomponent (can be explicitly exposed to the + * SysUIComponent, see {@link WMComponent}). + * + * This module only defines Shell dependencies for the TV SystemUI implementation. Common + * dependencies should go into {@link WMShellBaseModule}. */ @Module(includes = {TvPipModule.class}) public class TvWMShellModule { + + // + // Internal common - Components used internally by multiple shell features + // + @WMSingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, @@ -53,16 +63,20 @@ public class TvWMShellModule { transactionPool); } + // + // Split/multiwindow + // + @WMSingleton @Provides - static LegacySplitScreen provideSplitScreen(Context context, + static LegacySplitScreenController provideSplitScreen(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener, Transitions transitions, @ShellMainThread ShellExecutor mainExecutor, @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) { - return LegacySplitScreenController.create(context, displayController, systemWindows, + return new LegacySplitScreenController(context, displayController, systemWindows, displayImeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 81ac21c00823..ec61db591324 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -43,6 +43,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.model.SysUiState; @@ -73,7 +74,20 @@ import java.util.concurrent.Executor; import javax.inject.Inject; /** - * Proxy in SysUiScope to delegate events to controllers in WM Shell library. + * A SystemUI service that starts with the SystemUI application and sets up any bindings between + * Shell and SysUI components. This service starts happens after the {@link WMComponent} has + * already been initialized and may only reference Shell components that are explicitly exported to + * SystemUI (see {@link WMComponent}. + * + * eg. SysUI application starts + * -> SystemUIFactory is initialized + * -> WMComponent is created + * -> WMShellBaseModule dependencies are injected + * -> WMShellModule (form-factory specific) dependencies are injected + * -> SysUIComponent is created + * -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code + * -> SysUI services are started + * -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces */ @SysUISingleton public final class WMShell extends SystemUI @@ -142,6 +156,8 @@ public final class WMShell extends SystemUI @Override public void start() { + // TODO: Consider piping config change and other common calls to a shell component to + // delegate internally mProtoTracer.add(this); mCommandQueue.addCallback(this); mPipOptional.ifPresent(this::initPip); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index b42dde63d1c2..449db61a0fbb 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -32,6 +32,7 @@ import android.view.WindowManager; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.FullscreenTaskListener; @@ -45,6 +46,7 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.TaskViewFactoryController; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.DisplayController; @@ -63,6 +65,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; @@ -70,8 +73,8 @@ 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.SizeCompatUI; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -85,8 +88,13 @@ import dagger.Module; import dagger.Provides; /** - * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here - * should be shared among different branches of SystemUI. + * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only + * accessible from components within the WM subcomponent (can be explicitly exposed to the + * SysUIComponent, see {@link WMComponent}). + * + * This module only defines *common* dependencies across various SystemUI implementations, + * dependencies that are device/form factor SystemUI implementation specific should go into their + * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.) */ @Module public abstract class WMShellBaseModule { @@ -174,72 +182,65 @@ public abstract class WMShellBaseModule { } } + // + // Internal common - Components used internally by multiple shell features + // + @WMSingleton @Provides - static ShellInit provideShellInit(DisplayImeController displayImeController, - DragAndDropController dragAndDropController, - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, - Optional<SplitScreenController> splitScreenOptional, - Optional<AppPairs> appPairsOptional, - FullscreenTaskListener fullscreenTaskListener, - Transitions transitions, - @ShellMainThread ShellExecutor mainExecutor) { - return ShellInitImpl.create(displayImeController, - dragAndDropController, - shellTaskOrganizer, - legacySplitScreenOptional, - splitScreenOptional, - appPairsOptional, - fullscreenTaskListener, - transitions, - mainExecutor); + static DisplayController provideDisplayController(Context context, + IWindowManager wmService, @ShellMainThread ShellExecutor mainExecutor) { + return new DisplayController(context, wmService, mainExecutor); } - /** - * Note, this is only optional because we currently pass this to the SysUI component scope and - * for non-primary users, we may inject a null-optional for that dependency. - */ @WMSingleton @Provides - static Optional<ShellCommandHandler> provideShellCommandHandler( - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreen> legacySplitScreenOptional, - Optional<SplitScreenController> splitScreenOptional, - Optional<Pip> pipOptional, - Optional<OneHanded> oneHandedOptional, - Optional<HideDisplayCutout> hideDisplayCutout, - Optional<AppPairs> appPairsOptional, + static DragAndDropController provideDragAndDropController(Context context, + DisplayController displayController) { + return new DragAndDropController(context, displayController); + } + + @WMSingleton + @Provides + static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, + Context context, SizeCompatUIController sizeCompatUI) { + return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI); + } + + @WMSingleton + @Provides + static SizeCompatUIController provideSizeCompatUIController(Context context, + DisplayController displayController, DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) { - return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer, - legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, - hideDisplayCutout, appPairsOptional, mainExecutor)); + return new SizeCompatUIController(context, displayController, imeController, mainExecutor); } @WMSingleton @Provides - static TransactionPool provideTransactionPool() { - return new TransactionPool(); + static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool, + @ShellMainThread ShellExecutor mainExecutor) { + return new SyncTransactionQueue(pool, mainExecutor); } @WMSingleton @Provides - static DisplayController provideDisplayController(Context context, - IWindowManager wmService, @ShellMainThread ShellExecutor mainExecutor) { - return new DisplayController(context, wmService, mainExecutor); + static SystemWindows provideSystemWindows(DisplayController displayController, + IWindowManager wmService) { + return new SystemWindows(displayController, wmService); } + // We currently dedupe multiple messages, so we use the shell main handler directly @WMSingleton @Provides - static DragAndDropController provideDragAndDropController(Context context, - DisplayController displayController) { - return new DragAndDropController(context, displayController); + static TaskStackListenerImpl providerTaskStackListenerImpl( + @ShellMainThread Handler mainHandler) { + return new TaskStackListenerImpl(mainHandler); } @WMSingleton @Provides - static FloatingContentCoordinator provideFloatingContentCoordinator() { - return new FloatingContentCoordinator(); + static TransactionPool provideTransactionPool() { + return new TransactionPool(); } @WMSingleton @@ -249,10 +250,99 @@ public abstract class WMShellBaseModule { return new WindowManagerShellWrapper(mainExecutor); } + // + // Bubbles + // + + @WMSingleton + @Provides + static Optional<Bubbles> provideBubbles(Optional<BubbleController> bubbleController) { + return bubbleController.map((controller) -> controller.asBubbles()); + } + + // Note: Handler needed for LauncherApps.register + @WMSingleton + @Provides + static Optional<BubbleController> provideBubbleController(Context context, + FloatingContentCoordinator floatingContentCoordinator, + IStatusBarService statusBarService, + WindowManager windowManager, + WindowManagerShellWrapper windowManagerShellWrapper, + LauncherApps launcherApps, + UiEventLogger uiEventLogger, + ShellTaskOrganizer organizer, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { + return Optional.of(BubbleController.create(context, null /* synchronizer */, + floatingContentCoordinator, statusBarService, windowManager, + windowManagerShellWrapper, launcherApps, uiEventLogger, organizer, + mainExecutor, mainHandler)); + } + + // + // Fullscreen + // + + @WMSingleton + @Provides + static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) { + return new FullscreenTaskListener(syncQueue); + } + + // + // Hide display cutout + // + + @WMSingleton + @Provides + static Optional<HideDisplayCutout> provideHideDisplayCutout( + Optional<HideDisplayCutoutController> hideDisplayCutoutController) { + return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout()); + } + + @WMSingleton + @Provides + static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context, + DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) { + return Optional.ofNullable( + HideDisplayCutoutController.create(context, displayController, mainExecutor)); + } + + // + // One handed mode (optional feature) + // + + @WMSingleton + @Provides + static Optional<OneHanded> provideOneHanded(Optional<OneHandedController> oneHandedController) { + return oneHandedController.map((controller) -> controller.asOneHanded()); + } + + // Needs the shell main handler for ContentObserver callbacks + @WMSingleton + @Provides + static Optional<OneHandedController> provideOneHandedController(Context context, + DisplayController displayController, TaskStackListenerImpl taskStackListener, + UiEventLogger uiEventLogger, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { + return Optional.ofNullable(OneHandedController.create(context, displayController, + taskStackListener, uiEventLogger, mainExecutor, mainHandler)); + } + + // + // Pip (optional feature) + // + + @WMSingleton + @Provides + static FloatingContentCoordinator provideFloatingContentCoordinator() { + return new FloatingContentCoordinator(); + } + @WMSingleton @Provides static PipAppOpsListener providePipAppOpsListener(Context context, - IActivityManager activityManager, PipTouchHandler pipTouchHandler, @ShellMainThread ShellExecutor mainExecutor) { return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); @@ -268,38 +358,39 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger, - PackageManager packageManager) { - return new PipUiEventLogger(uiEventLogger, packageManager); - } - - @WMSingleton - @Provides static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { return new PipSurfaceTransactionHelper(context); } @WMSingleton @Provides - static SystemWindows provideSystemWindows(DisplayController displayController, - IWindowManager wmService) { - return new SystemWindows(displayController, wmService); + static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger, + PackageManager packageManager) { + return new PipUiEventLogger(uiEventLogger, packageManager); } + // + // Shell transitions + // + @WMSingleton @Provides - static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool, - @ShellMainThread ShellExecutor mainExecutor) { - return new SyncTransactionQueue(pool, mainExecutor); + static RemoteTransitions provideRemoteTransitions(Transitions transitions) { + return Transitions.asRemoteTransitions(transitions); } @WMSingleton @Provides - static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, - Context context, SizeCompatUI sizeCompatUI) { - return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI); + static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool, + @ShellMainThread ShellExecutor mainExecutor, + @ShellAnimationThread ShellExecutor animExecutor) { + return new Transitions(organizer, pool, mainExecutor, animExecutor); } + // + // Split/multiwindow + // + @WMSingleton @Provides static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer( @@ -307,17 +398,6 @@ public abstract class WMShellBaseModule { return new RootTaskDisplayAreaOrganizer(mainExecutor, context); } - // We currently dedupe multiple messages, so we use the shell main handler directly - @WMSingleton - @Provides - static TaskStackListenerImpl providerTaskStackListenerImpl( - @ShellMainThread Handler mainHandler) { - return new TaskStackListenerImpl(mainHandler); - } - - @BindsOptionalOf - abstract LegacySplitScreen optionalLegacySplitScreen(); - @WMSingleton @Provides static Optional<SplitScreen> provideSplitScreen( @@ -340,81 +420,91 @@ public abstract class WMShellBaseModule { } } - @BindsOptionalOf - abstract AppPairs optionalAppPairs(); + // Legacy split (optional feature) - // Note: Handler needed for LauncherApps.register @WMSingleton @Provides - static Optional<Bubbles> provideBubbles(Context context, - FloatingContentCoordinator floatingContentCoordinator, - IStatusBarService statusBarService, - WindowManager windowManager, - WindowManagerShellWrapper windowManagerShellWrapper, - LauncherApps launcherApps, - UiEventLogger uiEventLogger, - ShellTaskOrganizer organizer, - @ShellMainThread ShellExecutor mainExecutor, - @ShellMainThread Handler mainHandler) { - return Optional.of(BubbleController.create(context, null /* synchronizer */, - floatingContentCoordinator, statusBarService, windowManager, - windowManagerShellWrapper, launcherApps, uiEventLogger, organizer, - mainExecutor, mainHandler)); + static Optional<LegacySplitScreen> provideLegacySplitScreen( + Optional<LegacySplitScreenController> splitScreenController) { + return splitScreenController.map((controller) -> controller.asLegacySplitScreen()); } - // Needs the shell main handler for ContentObserver callbacks - @WMSingleton - @Provides - static Optional<OneHanded> provideOneHandedController(Context context, - DisplayController displayController, TaskStackListenerImpl taskStackListener, - UiEventLogger uiEventLogger, - @ShellMainThread ShellExecutor mainExecutor, - @ShellMainThread Handler mainHandler) { - return Optional.ofNullable(OneHandedController.create(context, displayController, - taskStackListener, uiEventLogger, mainExecutor, mainHandler)); - } + @BindsOptionalOf + abstract LegacySplitScreenController optionalLegacySplitScreenController(); - @WMSingleton - @Provides - static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context, - DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) { - return Optional.ofNullable( - HideDisplayCutoutController.create(context, displayController, mainExecutor)); - } + // App Pairs (optional feature) @WMSingleton @Provides - static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer, - @ShellMainThread ShellExecutor mainExecutor) { - return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor) - .getTaskViewFactory()); + static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) { + return appPairsController.map((controller) -> controller.asAppPairs()); } + @BindsOptionalOf + abstract AppPairsController optionalAppPairs(); + + // + // Task view factory + // + @WMSingleton @Provides - static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) { - return new FullscreenTaskListener(syncQueue); + static Optional<TaskViewFactory> provideTaskViewFactory( + TaskViewFactoryController taskViewFactoryController) { + return Optional.of(taskViewFactoryController.asTaskViewFactory()); } @WMSingleton @Provides - static RemoteTransitions provideRemoteTransitions(Transitions transitions) { - return Transitions.asRemoteTransitions(transitions); + static TaskViewFactoryController provideTaskViewFactoryController( + ShellTaskOrganizer shellTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { + return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor); } + // + // Misc + // + @WMSingleton @Provides - static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool, - @ShellMainThread ShellExecutor mainExecutor, - @ShellAnimationThread ShellExecutor animExecutor) { - return new Transitions(organizer, pool, mainExecutor, animExecutor); + static ShellInit provideShellInit(DisplayImeController displayImeController, + DragAndDropController dragAndDropController, + ShellTaskOrganizer shellTaskOrganizer, + Optional<LegacySplitScreenController> legacySplitScreenOptional, + Optional<SplitScreenController> splitScreenOptional, + Optional<AppPairsController> appPairsOptional, + FullscreenTaskListener fullscreenTaskListener, + Transitions transitions, + @ShellMainThread ShellExecutor mainExecutor) { + return ShellInitImpl.create(displayImeController, + dragAndDropController, + shellTaskOrganizer, + legacySplitScreenOptional, + splitScreenOptional, + appPairsOptional, + fullscreenTaskListener, + transitions, + mainExecutor); } + /** + * Note, this is only optional because we currently pass this to the SysUI component scope and + * for non-primary users, we may inject a null-optional for that dependency. + */ @WMSingleton @Provides - static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController, - DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) { - return SizeCompatUIController.create(context, displayController, imeController, - mainExecutor); + static Optional<ShellCommandHandler> provideShellCommandHandler( + ShellTaskOrganizer shellTaskOrganizer, + Optional<LegacySplitScreenController> legacySplitScreenOptional, + Optional<SplitScreenController> splitScreenOptional, + Optional<Pip> pipOptional, + Optional<OneHandedController> oneHandedOptional, + Optional<HideDisplayCutoutController> hideDisplayCutout, + Optional<AppPairsController> appPairsOptional, + @ShellMainThread ShellExecutor mainExecutor) { + return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer, + legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, + hideDisplayCutout, appPairsOptional, mainExecutor)); } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 2aaa0951d9d9..997b488a627f 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -21,10 +21,10 @@ import android.content.Context; import android.os.Handler; import android.view.IWindowManager; +import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; -import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; @@ -36,7 +36,6 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ChoreographerSfVsync; 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.Pip; import com.android.wm.shell.pip.PipAnimationController; @@ -60,11 +59,20 @@ import dagger.Module; import dagger.Provides; /** - * Provides dependencies from {@link com.android.wm.shell} which could be customized among different - * branches of SystemUI. + * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only + * accessible from components within the WM subcomponent (can be explicitly exposed to the + * SysUIComponent, see {@link WMComponent}). + * + * This module only defines Shell dependencies for handheld SystemUI implementation. Common + * dependencies should go into {@link WMShellBaseModule}. */ @Module(includes = WMShellBaseModule.class) public class WMShellModule { + + // + // Internal common - Components used internally by multiple shell features + // + @WMSingleton @Provides static DisplayImeController provideDisplayImeController(IWindowManager wmService, @@ -74,29 +82,37 @@ public class WMShellModule { transactionPool); } + // + // Split/multiwindow + // + @WMSingleton @Provides - static LegacySplitScreen provideLegacySplitScreen(Context context, + static LegacySplitScreenController provideLegacySplitScreen(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener, Transitions transitions, @ShellMainThread ShellExecutor mainExecutor, @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) { - return LegacySplitScreenController.create(context, displayController, systemWindows, + return new LegacySplitScreenController(context, displayController, systemWindows, displayImeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler); } @WMSingleton @Provides - static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer, + static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) { - return AppPairsController.create(shellTaskOrganizer, syncQueue, displayController, + return new AppPairsController(shellTaskOrganizer, syncQueue, displayController, mainExecutor); } + // + // Pip + // + @WMSingleton @Provides static Optional<Pip> providePip(Context context, DisplayController displayController, @@ -161,7 +177,8 @@ public class WMShellModule { PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, PipTransitionController pipTransitionController, - Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController, + Optional<LegacySplitScreenController> splitScreenOptional, + DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm, 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/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/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index 0dc268a3c186..fb817eac3e10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -16,6 +16,8 @@ package com.android.systemui.qs; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -28,6 +30,8 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.res.Configuration; +import android.content.res.Resources; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -37,6 +41,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaHost; @@ -44,6 +49,7 @@ import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.util.animation.DisappearParameters; import org.junit.Before; @@ -86,17 +92,25 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { QSTileView mQSTileView; @Mock PagedTileLayout mPagedTileLayout; + @Mock + FeatureFlags mFeatureFlags; + @Mock + Resources mResources; + @Mock + Configuration mConfiguration; private QSPanelControllerBase<QSPanel> mController; + + /** Implementation needed to ensure we have a reflectively-available class name. */ private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> { protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host, QSCustomizerController qsCustomizerController, MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager) { + DumpManager dumpManager, FeatureFlags featureFlags) { super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger, - qsLogger, dumpManager); + qsLogger, dumpManager, featureFlags); } @Override @@ -121,10 +135,12 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { when(mQSTileRevealControllerFactory.create(any(), any())) .thenReturn(mQSTileRevealController); when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters()); + when(mQSPanel.getResources()).thenReturn(mResources); + when(mResources.getConfiguration()).thenReturn(mConfiguration); mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost, mQSCustomizerController, mMediaHost, - mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager); + mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags); mController.init(); reset(mQSTileRevealController); @@ -136,7 +152,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost, mQSCustomizerController, mMediaHost, - mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) { + mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags) { @Override protected QSTileRevealController createTileRevealController() { return mQSTileRevealController; @@ -218,4 +234,18 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd"); verify(mPagedTileLayout).setListening(false, mUiEventLogger); } + + + @Test + public void testShouldUzeHorizontalLayout_falseForSplitShade() { + mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; + when(mMediaHost.getVisible()).thenReturn(true); + + when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false); + assertThat(mController.shouldUseHorizontalLayout()).isTrue(); + + when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true); + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true); + assertThat(mController.shouldUseHorizontalLayout()).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java index 4381158c3415..0dfebab59feb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java @@ -41,6 +41,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.settings.brightness.ToggleSlider; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.animation.DisappearParameters; @@ -93,6 +94,8 @@ public class QSPanelControllerTest extends SysuiTestCase { QSTileView mQSTileView; @Mock PagedTileLayout mPagedTileLayout; + @Mock + FeatureFlags mFeatureFlags; private QSPanelController mController; @@ -118,7 +121,9 @@ public class QSPanelControllerTest extends SysuiTestCase { mQSTileHost, mQSCustomizerController, true, mMediaHost, mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger, mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory, - /* labelsFlag */ false); + /* labelsFlag */ false, + mFeatureFlags + ); mController.init(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 107160f47f02..587020090433 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.statusbar.FeatureFlags import org.junit.After import org.junit.Before import org.junit.Test @@ -63,6 +64,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() { private lateinit var tileLayout: TileLayout @Mock private lateinit var tileView: QSTileView + @Mock + private lateinit var featureFlags: FeatureFlags private lateinit var controller: QuickQSPanelController @@ -84,7 +87,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() { uiEventLogger, qsLogger, dumpManager, - false + false, + featureFlags ) controller.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt new file mode 100644 index 000000000000..b7b967866d47 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -0,0 +1,267 @@ +/* + * 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.qs.tiles + +import android.os.Handler +import android.provider.Settings +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.lifecycle.LifecycleOwner +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlsServiceInfo +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.dagger.ControlsComponent +import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsUiController +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.GlobalSettings +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class DeviceControlsTileTest : SysuiTestCase() { + + @Mock + private lateinit var qsHost: QSHost + @Mock + private lateinit var metricsLogger: MetricsLogger + @Mock + private lateinit var statusBarStateController: StatusBarStateController + @Mock + private lateinit var activityStarter: ActivityStarter + @Mock + private lateinit var qsLogger: QSLogger + private lateinit var controlsComponent: ControlsComponent + @Mock + private lateinit var controlsUiController: ControlsUiController + @Mock + private lateinit var controlsListingController: ControlsListingController + @Mock + private lateinit var controlsController: ControlsController + @Mock + private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var controlsDialog: ControlsDialog + private lateinit var globalSettings: GlobalSettings + @Mock + private lateinit var serviceInfo: ControlsServiceInfo + @Mock + private lateinit var uiEventLogger: UiEventLogger + @Captor + private lateinit var listingCallbackCaptor: + ArgumentCaptor<ControlsListingController.ControlsListingCallback> + + private lateinit var testableLooper: TestableLooper + private lateinit var tile: DeviceControlsTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + + `when`(qsHost.context).thenReturn(mContext) + `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + + controlsComponent = ControlsComponent( + true, + { controlsController }, + { controlsUiController }, + { controlsListingController } + ) + + globalSettings = FakeSettings() + + globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1) + `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true) + + tile = createTile() + } + + @Test + fun testAvailable() { + assertThat(tile.isAvailable).isTrue() + } + + @Test + fun testNotAvailableFeature() { + `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false) + + assertThat(tile.isAvailable).isFalse() + } + + @Test + fun testNotAvailableControls() { + controlsComponent = ControlsComponent( + false, + { controlsController }, + { controlsUiController }, + { controlsListingController } + ) + tile = createTile() + + assertThat(tile.isAvailable).isFalse() + } + + @Test + fun testNotAvailableFlag() { + globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0) + tile = createTile() + + assertThat(tile.isAvailable).isFalse() + } + + @Test + fun testObservingCallback() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + any(ControlsListingController.ControlsListingCallback::class.java) + ) + } + + @Test + fun testLongClickIntent() { + assertThat(tile.longClickIntent.action).isEqualTo(Settings.ACTION_DEVICE_CONTROLS_SETTINGS) + } + + @Test + fun testUnavailableByDefault() { + assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun testStateUnavailableIfNoListings() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + capture(listingCallbackCaptor) + ) + + listingCallbackCaptor.value.onServicesUpdated(emptyList()) + testableLooper.processAllMessages() + + assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun testStateAvailableIfListings() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + capture(listingCallbackCaptor) + ) + + listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) + testableLooper.processAllMessages() + + assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE) + } + + @Test + fun testMoveBetweenStates() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + capture(listingCallbackCaptor) + ) + + listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) + testableLooper.processAllMessages() + + listingCallbackCaptor.value.onServicesUpdated(emptyList()) + testableLooper.processAllMessages() + + assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun testNoDialogWhenUnavailable() { + tile.click() + testableLooper.processAllMessages() + + verify(controlsDialog, never()).show(any(ControlsUiController::class.java)) + } + + @Test + fun testDialogShowWhenAvailable() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + capture(listingCallbackCaptor) + ) + + listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) + testableLooper.processAllMessages() + + tile.click() + testableLooper.processAllMessages() + + verify(controlsDialog).show(controlsUiController) + } + + @Test + fun testDialogDismissedOnDestroy() { + verify(controlsListingController).observe( + any(LifecycleOwner::class.java), + capture(listingCallbackCaptor) + ) + + listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) + testableLooper.processAllMessages() + + tile.click() + testableLooper.processAllMessages() + + tile.destroy() + testableLooper.processAllMessages() + verify(controlsDialog).dismiss() + } + + private fun createTile(): DeviceControlsTile { + return DeviceControlsTile( + qsHost, + testableLooper.looper, + Handler(testableLooper.looper), + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + controlsComponent, + featureFlags, + { controlsDialog }, + globalSettings + ) + } +}
\ No newline at end of file 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/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..fa253e62ef0a 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 @@ -35,7 +35,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.tuner.TunerService; @@ -58,7 +57,6 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private PowerManager mPowerManager; @Mock private TunerService mTunerService; @Mock private BatteryController mBatteryController; - @Mock private FeatureFlags mFeatureFlags; @Before public void setup() { @@ -69,8 +67,7 @@ public class DozeParametersTest extends SysuiTestCase { mAlwaysOnDisplayPolicy, mPowerManager, mBatteryController, - mTunerService, - mFeatureFlags + mTunerService ); } @Test 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..4162884680a9 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, - mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */, - 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, - mQsExpansion, mCutoutTopInset); + mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, + 0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock, + mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */, + 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..d0e70310810e 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; @@ -58,6 +59,7 @@ import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; 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; @@ -68,7 +70,6 @@ import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.doze.DozeLog; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; -import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardAffordanceView; @@ -193,6 +194,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; @Mock + private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent; + @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; @@ -216,6 +219,9 @@ public class NotificationPanelViewTest extends SysuiTestCase { private NotificationsQuickSettingsContainer mNotificationContainerParent; @Mock private AmbientState mAmbientState; + @Mock + private UserManager mUserManager; + private NotificationPanelViewController mNotificationPanelViewController; private View.AccessibilityDelegate mAccessibiltyDelegate; @@ -299,11 +305,12 @@ public class NotificationPanelViewTest extends SysuiTestCase { mBiometricUnlockController, mStatusBarKeyguardViewManager, mNotificationStackScrollLayoutController, mKeyguardStatusViewComponentFactory, + mKeyguardUserSwitcherComponent, mGroupManager, mNotificationAreaController, mAuthController, - new QSDetailDisplayer(), mScrimController, + mUserManager, mMediaDataManager, mAmbientState, mFeatureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 253460db0d07..cae488a561a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -97,7 +97,6 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -259,7 +258,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private DemoModeController mDemoModeController; @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; @Mock private BrightnessSlider.Factory mBrightnessSliderFactory; - @Mock private FeatureFlags mFeatureFlags; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -420,8 +418,7 @@ public class StatusBarTest extends SysuiTestCase { mNotificationShadeDepthControllerLazy, mStatusBarTouchableRegionManager, mNotificationIconAreaController, - mBrightnessSliderFactory, - mFeatureFlags); + mBrightnessSliderFactory); when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn( mLockIconContainer); 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/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index edaff5ff0f10..45828c3f73ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -32,13 +32,18 @@ import static com.android.systemui.theme.ThemeOverlayApplier.THEME_CATEGORIES; 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; +import android.content.om.OverlayManagerTransaction; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -70,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)); } } @@ -87,6 +93,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { OverlayManager mOverlayManager; @Mock DumpManager mDumpManager; + @Mock + OverlayManagerTransaction.Builder mTransactionBuilder; private ThemeOverlayApplier mManager; @@ -94,7 +102,12 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { public void setup() throws Exception { MockitoAnnotations.initMocks(this); mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(), - LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager); + LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) { + @Override + protected OverlayManagerTransaction.Builder getTransactionBuilder() { + return mTransactionBuilder; + } + }; when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR, @@ -147,24 +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(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); + 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(mOverlayManager).setEnabledExclusiveInCategory( - entry.getValue(), UserHandle.SYSTEM); + verify(mTransactionBuilder).setEnabled(eq(entry.getValue()), eq(true), + eq(UserHandle.SYSTEM.getIdentifier())); } else { - verify(mOverlayManager, never()).setEnabledExclusiveInCategory( - entry.getValue(), UserHandle.SYSTEM); + verify(mTransactionBuilder, never()).setEnabled( + eq(entry.getValue()), eq(true), eq(UserHandle.SYSTEM.getIdentifier())); } } } @@ -174,17 +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(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); - verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle); + 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)); @@ -192,48 +224,56 @@ 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(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); + for (OverlayIdentifier overlayPackage : categoryToPackage.values()) { + verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), + eq(TEST_USER.getIdentifier())); } - verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS, - false, TEST_USER); - verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID, - false, TEST_USER); + verify(mTransactionBuilder).setEnabled( + eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)), + eq(false), eq(TEST_USER.getIdentifier())); + verify(mTransactionBuilder).setEnabled( + eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID)), + eq(false), eq(TEST_USER.getIdentifier())); } @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(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER); + verify(mTransactionBuilder).setEnabled( + eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + category)), eq(false), + eq(TEST_USER.getIdentifier())); } } @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(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER); - verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category", - TEST_USER); + verify(mTransactionBuilder, never()).setEnabled( + eq(new OverlayIdentifier("com.example.blah.category")), eq(false), + eq(TEST_USER.getIdentifier())); + verify(mTransactionBuilder, never()).setEnabled( + eq(new OverlayIdentifier("com.example.blah.category")), eq(true), + eq(TEST_USER.getIdentifier())); } @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, @@ -247,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/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java index c743fd07c492..365c62cddbdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -57,8 +57,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; +import com.android.systemui.statusbar.FeatureFlags; import org.junit.Before; import org.junit.Test; @@ -88,7 +87,6 @@ public class ToastUITest extends SysuiTestCase { private static final String TEXT = "Hello World"; private static final int MESSAGE_RES_ID = R.id.message; - private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock()); private Context mContextSpy; private ToastUI mToastUI; @Mock private LayoutInflater mLayoutInflater; @@ -99,6 +97,7 @@ public class ToastUITest extends SysuiTestCase { @Mock private PluginManager mPluginManager; @Mock private DumpManager mDumpManager; @Mock private ToastLogger mToastLogger; + @Mock private FeatureFlags mFeatureFlags; @Mock private ITransientNotificationCallback mCallback; @Captor private ArgumentCaptor<View> mViewCaptor; @@ -107,12 +106,9 @@ public class ToastUITest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - - // This is because inflate will result in WindowManager (WM) calls, which will fail since we - // are mocking it, so we mock LayoutInflater with the view obtained before mocking WM. - View view = ToastPresenter.getTextToastView(mContext, TEXT); - when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(view); - mContext.addMockSystemService(LayoutInflater.class, mLayoutInflater); + when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn( + ToastPresenter.getTextToastView(mContext, TEXT)); + when(mFeatureFlags.isToastStyleEnabled()).thenReturn(false); mContext.addMockSystemService(WindowManager.class, mWindowManager); mContextSpy = spy(mContext); @@ -120,8 +116,8 @@ public class ToastUITest extends SysuiTestCase { doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt()); mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager, - mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager), - mFakeDelayableExecutor, mToastLogger); + mAccessibilityManager, new ToastFactory(mLayoutInflater, mPluginManager, + mDumpManager, mFeatureFlags), mToastLogger); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 76269dda8245..f1fc0b7723fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -89,8 +89,6 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; @@ -297,7 +295,7 @@ public class BubblesTest extends SysuiTestCase { mBubblesManager = new BubblesManager( mContext, - mBubbleController.getImpl(), + mBubbleController.asBubbles(), mNotificationShadeWindowController, mStatusBarStateController, mShadeController, 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/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 5340ff7e967c..9e10b21ce3b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -261,7 +261,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubblesManager = new BubblesManager( mContext, - mBubbleController.getImpl(), + mBubbleController.asBubbles(), mNotificationShadeWindowController, mStatusBarStateController, mShadeController, diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 6c30999f63a4..38275f7cd348 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1245,10 +1245,9 @@ public class BackupManagerService extends IBackupManager.Stub { @Override public IRestoreSession beginRestoreSessionForUser( - int userId, String packageName, String transportID, - @OperationType int operationType) throws RemoteException { + int userId, String packageName, String transportID) throws RemoteException { return isUserReadyForBackup(userId) - ? beginRestoreSession(userId, packageName, transportID, operationType) : null; + ? beginRestoreSession(userId, packageName, transportID) : null; } /** @@ -1257,15 +1256,13 @@ public class BackupManagerService extends IBackupManager.Stub { */ @Nullable public IRestoreSession beginRestoreSession( - @UserIdInt int userId, String packageName, String transportName, - @OperationType int operationType) { + @UserIdInt int userId, String packageName, String transportName) { UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()"); return userBackupManagerService == null ? null - : userBackupManagerService.beginRestoreSession(packageName, transportName, - operationType); + : userBackupManagerService.beginRestoreSession(packageName, transportName); } @Override @@ -1350,15 +1347,15 @@ public class BackupManagerService extends IBackupManager.Stub { if (!isUserReadyForBackup(userId)) { return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } - return requestBackup(userId, packages, observer, monitor, flags, OperationType.BACKUP); + return requestBackup(userId, packages, observer, monitor, flags); } @Override public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags, @OperationType int operationType) + IBackupManagerMonitor monitor, int flags) throws RemoteException { return requestBackup(binderGetCallingUserId(), packages, - observer, monitor, flags, operationType); + observer, monitor, flags); } /** @@ -1370,15 +1367,13 @@ public class BackupManagerService extends IBackupManager.Stub { String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, - int flags, - @OperationType int operationType) { + int flags) { UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission(userId, "requestBackup()"); return userBackupManagerService == null ? BackupManager.ERROR_BACKUP_NOT_ALLOWED - : userBackupManagerService.requestBackup(packages, observer, monitor, flags, - operationType); + : userBackupManagerService.requestBackup(packages, observer, monitor, flags); } @Override diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 136cd22fad83..faec95f142eb 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -127,6 +127,7 @@ 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; @@ -1860,19 +1861,10 @@ public class UserBackupManagerService { /** * Requests a backup for the inputted {@code packages} with a specified {@link - * 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) { + IBackupManagerMonitor monitor, int flags) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup"); if (packages == null || packages.length < 1) { @@ -1903,13 +1895,16 @@ public class UserBackupManagerService { final TransportClient transportClient; final String transportDirName; + int operationType; try { transportDirName = mTransportManager.getTransportDirName( mTransportManager.getCurrentTransportName()); transportClient = mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()"); - } catch (TransportNotRegisteredException e) { + operationType = getOperationTypeFromTransport(transportClient); + } catch (TransportNotRegisteredException | TransportNotAvailableException + | RemoteException e) { BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); monitor = BackupManagerMonitorUtils.monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, @@ -4024,15 +4019,13 @@ public class UserBackupManagerService { } /** Hand off a restore session. */ - public IRestoreSession beginRestoreSession(String packageName, String transport, - @OperationType int operationType) { + public IRestoreSession beginRestoreSession(String packageName, String transport) { if (DEBUG) { Slog.v( TAG, addUserIdToLogMessage( mUserId, - "beginRestoreSession: pkg=" + packageName + " transport=" + transport - + "operationType=" + operationType)); + "beginRestoreSession: pkg=" + packageName + " transport=" + transport)); } boolean needPermission = true; @@ -4073,6 +4066,17 @@ 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( @@ -4356,6 +4360,23 @@ 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/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index a6cfae492db9..c6a8660d8797 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -2,7 +2,7 @@ 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, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com +per-file VibratorManagerService.java, 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/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 00d8b0f1bed4..d10cf4dd0505 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -30,6 +30,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; +import android.text.TextUtils; import android.util.Slog; import com.android.internal.R; @@ -147,14 +148,15 @@ public class PersistentDataBlockService extends SystemService { private int getAllowedUid(int userHandle) { String allowedPackage = mContext.getResources() .getString(R.string.config_persistentDataPackageName); - PackageManager pm = mContext.getPackageManager(); int allowedUid = -1; - try { - allowedUid = pm.getPackageUidAsUser(allowedPackage, - PackageManager.MATCH_SYSTEM_ONLY, userHandle); - } catch (PackageManager.NameNotFoundException e) { - // not expected - Slog.e(TAG, "not able to find package " + allowedPackage, e); + if (!TextUtils.isEmpty(allowedPackage)) { + try { + allowedUid = mContext.getPackageManager().getPackageUidAsUser( + allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle); + } catch (PackageManager.NameNotFoundException e) { + // not expected + Slog.e(TAG, "not able to find package " + allowedPackage, e); + } } return allowedUid; } diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java index e7e5d67ff9f4..d264f8570cf2 100644 --- a/services/core/java/com/android/server/VibratorManagerService.java +++ b/services/core/java/com/android/server/VibratorManagerService.java @@ -18,9 +18,14 @@ package com.android.server; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AppOpsManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.hardware.vibrator.IVibrator; import android.os.BatteryStats; import android.os.Binder; @@ -74,6 +79,7 @@ import java.util.function.Function; /** System implementation of {@link IVibratorManagerService}. */ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String TAG = "VibratorManagerService"; + private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); @@ -89,7 +95,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Override public void onStart() { mService = new VibratorManagerService(getContext(), new Injector()); - publishBinderService("vibrator_manager", mService); + publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService); } @Override @@ -105,6 +111,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private final Object mLock = new Object(); private final Context mContext; + private final String mSystemUiPackage; private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; private final Handler mHandler; @@ -128,6 +135,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private VibrationScaler mVibrationScaler; private InputDeviceDelegate mInputDeviceDelegate; + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + synchronized (mLock) { + // When the system is entering a non-interactive state, we want + // to cancel vibrations in case a misbehaving app has abandoned + // them. However it may happen that the system is currently playing + // haptic feedback as part of the transition. So we don't cancel + // system vibrations. + if (mCurrentVibration != null + && !isSystemHapticFeedback(mCurrentVibration.getVibration())) { + mNextVibration = null; + mCurrentVibration.cancel(); + } + } + } + } + }; + static native long nativeInit(OnSyncedVibrationCompleteListener listener); static native long nativeGetFinalizer(); @@ -155,6 +182,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { com.android.internal.R.integer.config_previousVibrationsDumpLimit); mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit); + mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) + .getSystemUiServiceComponent().getPackageName(); + mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); @@ -184,6 +214,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { for (int i = 0; i < mVibrators.size(); i++) { mVibrators.valueAt(i).off(); } + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + context.registerReceiver(mIntentReceiver, filter); + + injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); } /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */ @@ -371,7 +407,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibratorManagerRecords.record(mCurrentExternalVibration); mCurrentExternalVibration.externalVibration.mute(); mCurrentExternalVibration = null; - // TODO(b/167946816): set external control to false + setExternalControl(false); } } finally { Binder.restoreCallingIdentity(ident); @@ -432,6 +468,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + private void setExternalControl(boolean externalControl) { + for (int i = 0; i < mVibrators.size(); i++) { + mVibrators.valueAt(i).setExternalControl(externalControl); + } + } + @GuardedBy("mLock") private void updateAlwaysOnLocked(AlwaysOnVibration vib) { for (int i = 0; i < vib.effects.size(); i++) { @@ -507,6 +549,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @GuardedBy("mLock") + private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) { + vib.end(status); + mVibratorManagerRecords.record(vib); + } + + @GuardedBy("mLock") private void reportFinishedVibrationLocked(Vibration.Status status) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked"); Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); @@ -827,6 +875,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { == PackageManager.PERMISSION_GRANTED; } + private boolean isSystemHapticFeedback(Vibration vib) { + if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) { + return false; + } + return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg); + } + @GuardedBy("mLock") private void onAllVibratorsLocked(Consumer<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { @@ -859,6 +914,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibratorController.OnVibrationCompleteListener listener) { return new VibratorController(vibratorId, listener); } + + void addService(String name, IBinder service) { + ServiceManager.addService(name, service); + } } /** @@ -1088,14 +1147,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } pw.println(); pw.println(" mCurrentVibration:"); - pw.println(" " + mCurrentVibration == null - ? null : mCurrentVibration.getVibration().getDebugInfo()); + pw.println(" " + (mCurrentVibration == null + ? null : mCurrentVibration.getVibration().getDebugInfo())); pw.println(" mNextVibration:"); - pw.println(" " + mNextVibration == null - ? null : mNextVibration.getVibration().getDebugInfo()); + pw.println(" " + (mNextVibration == null + ? null : mNextVibration.getVibration().getDebugInfo())); pw.println(" mCurrentExternalVibration:"); - pw.println(" " + mCurrentExternalVibration == null - ? null : mCurrentExternalVibration.getDebugInfo()); + pw.println(" " + (mCurrentExternalVibration == null + ? null : mCurrentExternalVibration.getDebugInfo())); pw.println(); pw.println(" mVibrationSettings=" + mVibrationSettings); for (int i = 0; i < mPreviousVibrations.size(); i++) { @@ -1168,6 +1227,145 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */ + private final class ExternalVibratorService extends IExternalVibratorService.Stub { + ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient; + + @Override + public int onExternalVibrationStart(ExternalVibration vib) { + if (!hasExternalControlCapability()) { + return IExternalVibratorService.SCALE_MUTE; + } + if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE, + vib.getUid(), -1 /*owningUid*/, true /*exported*/) + != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() + + " tried to play externally controlled vibration" + + " without VIBRATE permission, ignoring."); + return IExternalVibratorService.SCALE_MUTE; + } + + int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(), + vib.getVibrationAttributes()); + if (mode != AppOpsManager.MODE_ALLOWED) { + ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); + vibHolder.scale = IExternalVibratorService.SCALE_MUTE; + if (mode == AppOpsManager.MODE_ERRORED) { + Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid()); + endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS); + } else { + endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS); + } + return vibHolder.scale; + } + + VibrationThread cancelingVibration = null; + int scale; + synchronized (mLock) { + if (mCurrentExternalVibration != null + && mCurrentExternalVibration.externalVibration.equals(vib)) { + // We are already playing this external vibration, so we can return the same + // scale calculated in the previous call to this method. + return mCurrentExternalVibration.scale; + } + if (mCurrentExternalVibration == null) { + // If we're not under external control right now, then cancel any normal + // vibration that may be playing and ready the vibrator for external control. + if (mCurrentVibration != null) { + mNextVibration = null; + mCurrentVibration.cancel(); + cancelingVibration = mCurrentVibration; + } + } else { + endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); + } + // At this point we either have an externally controlled vibration playing, or + // no vibration playing. Since the interface defines that only one externally + // controlled vibration can play at a time, by returning something other than + // SCALE_MUTE from this function we can be assured that if we are currently + // playing vibration, it will be muted in favor of the new vibration. + // + // Note that this doesn't support multiple concurrent external controls, as we + // would need to mute the old one still if it came from a different controller. + mCurrentExternalVibration = new ExternalVibrationHolder(vib); + mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient(); + vib.linkToDeath(mCurrentExternalDeathRecipient); + mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( + vib.getVibrationAttributes().getUsage()); + scale = mCurrentExternalVibration.scale; + } + + if (cancelingVibration != null) { + try { + cancelingVibration.join(); + } catch (InterruptedException e) { + Slog.w("Interrupted while waiting for vibration to finish before starting " + + "external control", e); + } + } + if (DEBUG) { + Slog.d(TAG, "Vibrator going under external control."); + } + setExternalControl(true); + if (DEBUG) { + Slog.e(TAG, "Playing external vibration: " + vib); + } + return scale; + } + + @Override + public void onExternalVibrationStop(ExternalVibration vib) { + synchronized (mLock) { + if (mCurrentExternalVibration != null + && mCurrentExternalVibration.externalVibration.equals(vib)) { + if (DEBUG) { + Slog.e(TAG, "Stopping external vibration" + vib); + } + stopExternalVibrateLocked(Vibration.Status.FINISHED); + } + } + } + + private void stopExternalVibrateLocked(Vibration.Status status) { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked"); + try { + if (mCurrentExternalVibration == null) { + return; + } + endVibrationLocked(mCurrentExternalVibration, status); + mCurrentExternalVibration.externalVibration.unlinkToDeath( + mCurrentExternalDeathRecipient); + mCurrentExternalDeathRecipient = null; + mCurrentExternalVibration = null; + setExternalControl(false); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + private boolean hasExternalControlCapability() { + for (int i = 0; i < mVibrators.size(); i++) { + if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { + return true; + } + } + return false; + } + + private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient { + public void binderDied() { + synchronized (mLock) { + if (mCurrentExternalVibration != null) { + if (DEBUG) { + Slog.d(TAG, "External vibration finished because binder died"); + } + stopExternalVibrateLocked(Vibration.Status.CANCELLED); + } + } + } + } + } + /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */ private final class VibratorManagerShellCommand extends ShellCommand { public static final String SHELL_PACKAGE_NAME = "com.android.shell"; diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java deleted file mode 100644 index 2ac365d6d11b..000000000000 --- a/services/core/java/com/android/server/VibratorService.java +++ /dev/null @@ -1,1243 +0,0 @@ -/* - * Copyright (C) 2008 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 android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.hardware.vibrator.IVibrator; -import android.os.BatteryStats; -import android.os.Binder; -import android.os.CombinedVibrationEffect; -import android.os.ExternalVibration; -import android.os.Handler; -import android.os.IBinder; -import android.os.IExternalVibratorService; -import android.os.IVibratorService; -import android.os.IVibratorStateListener; -import android.os.Looper; -import android.os.PowerManager; -import android.os.Process; -import android.os.ResultReceiver; -import android.os.ServiceManager; -import android.os.ShellCallback; -import android.os.ShellCommand; -import android.os.Trace; -import android.os.VibrationAttributes; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.os.VibratorInfo; -import android.util.Slog; -import android.util.proto.ProtoOutputStream; - -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.vibrator.VibratorController.OnVibrationCompleteListener; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.concurrent.atomic.AtomicInteger; - -/** System implementation of {@link IVibratorService}. */ -public class VibratorService extends IVibratorService.Stub { - private static final String TAG = "VibratorService"; - private static final boolean DEBUG = false; - private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; - - // Default vibration attributes. Used when vibration is requested without attributes - private static final VibrationAttributes DEFAULT_ATTRIBUTES = - new VibrationAttributes.Builder().build(); - - // Used to generate globally unique vibration ids. - private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback - - private final LinkedList<Vibration.DebugInfo> mPreviousRingVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousNotificationVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousAlarmVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousVibrations; - private final int mPreviousVibrationsLimit; - private final Handler mH; - private final Object mLock = new Object(); - private final VibratorController mVibratorController; - private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks(); - - private final Context mContext; - private final PowerManager.WakeLock mWakeLock; - private final AppOpsManager mAppOps; - private final IBatteryStats mBatteryStatsService; - private final String mSystemUiPackage; - private VibrationSettings mVibrationSettings; - private VibrationScaler mVibrationScaler; - private InputDeviceDelegate mInputDeviceDelegate; - - @GuardedBy("mLock") - private VibrationThread mThread; - @GuardedBy("mLock") - private VibrationThread mNextVibrationThread; - - @GuardedBy("mLock") - private Vibration mCurrentVibration; - private int mCurVibUid = -1; - private ExternalVibrationHolder mCurrentExternalVibration; - - /** - * Implementation of {@link VibrationThread.VibrationCallbacks} that reports finished - * vibrations. - */ - private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks { - - @Override - public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) { - return false; - } - - @Override - public boolean triggerSyncedVibration(long vibrationId) { - return false; - } - - @Override - public void cancelSyncedVibration() { - } - - @Override - public void onVibrationEnded(long vibrationId, Vibration.Status status) { - if (DEBUG) { - Slog.d(TAG, "Vibration thread finished with status " + status); - } - synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) { - mThread = null; - reportFinishVibrationLocked(status); - if (mNextVibrationThread != null) { - startVibrationThreadLocked(mNextVibrationThread); - mNextVibrationThread = null; - } - } - } - } - } - - /** - * Implementation of {@link OnVibrationCompleteListener} with a weak reference to this service. - */ - private static final class VibrationCompleteListener implements OnVibrationCompleteListener { - private WeakReference<VibratorService> mServiceRef; - - VibrationCompleteListener(VibratorService service) { - mServiceRef = new WeakReference<>(service); - } - - @Override - public void onComplete(int vibratorId, long vibrationId) { - VibratorService service = mServiceRef.get(); - if (service != null) { - service.onVibrationComplete(vibratorId, vibrationId); - } - } - } - - /** Holder for a {@link ExternalVibration}. */ - private final class ExternalVibrationHolder { - - public final ExternalVibration externalVibration; - public int scale; - - private final long mStartTimeDebug; - private long mEndTimeDebug; - private Vibration.Status mStatus; - - private ExternalVibrationHolder(ExternalVibration externalVibration) { - this.externalVibration = externalVibration; - this.scale = IExternalVibratorService.SCALE_NONE; - mStartTimeDebug = System.currentTimeMillis(); - mStatus = Vibration.Status.RUNNING; - } - - public void end(Vibration.Status status) { - if (mStatus != Vibration.Status.RUNNING) { - // Vibration already ended, keep first ending status set and ignore this one. - return; - } - mStatus = status; - mEndTimeDebug = System.currentTimeMillis(); - } - - public Vibration.DebugInfo getDebugInfo() { - return new Vibration.DebugInfo( - mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null, - scale, externalVibration.getVibrationAttributes(), - externalVibration.getUid(), externalVibration.getPackage(), - /* reason= */ null, mStatus); - } - } - - VibratorService(Context context) { - this(context, new Injector()); - } - - @VisibleForTesting - VibratorService(Context context, Injector injector) { - mH = injector.createHandler(Looper.myLooper()); - mVibratorController = injector.createVibratorController( - new VibrationCompleteListener(this)); - - // Reset the hardware to a default state, in case this is a runtime - // restart instead of a fresh boot. - mVibratorController.off(); - - mContext = context; - PowerManager pm = context.getSystemService(PowerManager.class); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); - mWakeLock.setReferenceCounted(true); - - mAppOps = mContext.getSystemService(AppOpsManager.class); - mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService( - BatteryStats.SERVICE_NAME)); - mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) - .getSystemUiServiceComponent().getPackageName(); - - mPreviousVibrationsLimit = mContext.getResources().getInteger( - com.android.internal.R.integer.config_previousVibrationsDumpLimit); - - mPreviousRingVibrations = new LinkedList<>(); - mPreviousNotificationVibrations = new LinkedList<>(); - mPreviousAlarmVibrations = new LinkedList<>(); - mPreviousVibrations = new LinkedList<>(); - mPreviousExternalVibrations = new LinkedList<>(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - context.registerReceiver(mIntentReceiver, filter); - - injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); - } - - public void systemReady() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady"); - try { - mVibrationSettings = new VibrationSettings(mContext, mH); - mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); - mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH); - - mVibrationSettings.addListener(this::updateVibrators); - - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mVibrationSettings.updateSettings(); - } - }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH); - - updateVibrators(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - /** Callback for when vibration is complete, to be called by native. */ - @VisibleForTesting - public void onVibrationComplete(int vibratorId, long vibrationId) { - synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.id == vibrationId - && mThread != null) { - if (DEBUG) { - Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread"); - } - // Let the thread playing the vibration handle the callback, since it might be - // expecting the vibrator to turn off multiple times during a single vibration. - mThread.vibratorComplete(vibratorId); - } - } - } - - @Override // Binder call - public boolean hasVibrator() { - // For now, we choose to ignore the presence of input devices that have vibrators - // when reporting whether the device has a vibrator. Applications often use this - // information to decide whether to enable certain features so they expect the - // result of hasVibrator() to be constant. For now, just report whether - // the device has a built-in vibrator. - return mVibratorController.isAvailable(); - } - - @Override // Binder call - public boolean isVibrating() { - if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) { - throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission"); - } - return mVibratorController.isVibrating(); - } - - @Override // Binder call - public VibratorInfo getVibratorInfo() { - return mVibratorController.getVibratorInfo(); - } - - @Override // Binder call - public boolean registerVibratorStateListener(IVibratorStateListener listener) { - if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) { - throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission"); - } - return mVibratorController.registerVibratorStateListener(listener); - } - - @Override // Binder call - @GuardedBy("mLock") - public boolean unregisterVibratorStateListener(IVibratorStateListener listener) { - if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) { - throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission"); - } - return mVibratorController.unregisterVibratorStateListener(listener); - } - - @Override // Binder call - public boolean hasAmplitudeControl() { - // Input device vibrators always support amplitude controls. - return mInputDeviceDelegate.isAvailable() - || mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL); - } - - private void verifyIncomingUid(int uid) { - if (uid == Binder.getCallingUid()) { - return; - } - if (Binder.getCallingPid() == Process.myPid()) { - return; - } - mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), null); - } - - /** - * Validate the incoming VibrationEffect. - * - * We can't throw exceptions here since we might be called from some system_server component, - * which would bring the whole system down. - * - * @return whether the VibrationEffect is valid - */ - private static boolean verifyVibrationEffect(VibrationEffect effect) { - if (effect == null) { - // Effect must not be null. - Slog.wtf(TAG, "effect must not be null"); - return false; - } - try { - effect.validate(); - } catch (Exception e) { - Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e); - return false; - } - return true; - } - - private VibrationEffect fixupVibrationEffect(VibrationEffect effect) { - if (effect instanceof VibrationEffect.Prebaked - && ((VibrationEffect.Prebaked) effect).shouldFallback()) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId()); - return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(), - fallback); - } - return effect; - } - - private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) { - if (attrs == null) { - attrs = DEFAULT_ATTRIBUTES; - } - if (shouldBypassDnd(attrs)) { - if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { - final int flags = attrs.getFlags() - & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; - attrs = new VibrationAttributes.Builder(attrs) - .setFlags(flags, attrs.getFlags()).build(); - } - } - - return attrs; - } - - @Override // Binder call - public void vibrate(int uid, String opPkg, VibrationEffect effect, - @Nullable VibrationAttributes attrs, String reason, IBinder token) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason); - try { - if (!hasPermission(android.Manifest.permission.VIBRATE)) { - throw new SecurityException("Requires VIBRATE permission"); - } - if (token == null) { - Slog.e(TAG, "token must not be null"); - return; - } - verifyIncomingUid(uid); - if (!verifyVibrationEffect(effect)) { - return; - } - effect = fixupVibrationEffect(effect); - attrs = fixupVibrationAttributes(attrs); - Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), - CombinedVibrationEffect.createSynced(effect), attrs, uid, opPkg, reason); - - // If our current vibration is longer than the new vibration and is the same amplitude, - // then just let the current one finish. - synchronized (mLock) { - VibrationEffect currentEffect = - mCurrentVibration == null ? null : getEffect(mCurrentVibration); - if (effect instanceof VibrationEffect.OneShot - && currentEffect instanceof VibrationEffect.OneShot) { - VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect; - VibrationEffect.OneShot currentOneShot = - (VibrationEffect.OneShot) currentEffect; - if (currentOneShot.getDuration() > newOneShot.getDuration() - && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) { - if (DEBUG) { - Slog.d(TAG, - "Ignoring incoming vibration in favor of current vibration"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ONGOING); - return; - } - } - - - // If something has external control of the vibrator, assume that it's more - // important for now. - if (mCurrentExternalVibration != null) { - if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration for current external vibration"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_EXTERNAL); - return; - } - - // If the current vibration is repeating and the incoming one is non-repeating, - // then ignore the non-repeating vibration. This is so that we don't cancel - // vibrations that are meant to grab the attention of the user, like ringtones and - // alarms, in favor of one-shot vibrations that are likely quite short. - if (!isRepeatingVibration(effect) - && mCurrentVibration != null - && isRepeatingVibration(currentEffect)) { - if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ALARM); - return; - } - - if (!mVibrationSettings.shouldVibrateForUid(uid, vib.attrs.getUsage())) { - Slog.e(TAG, "Ignoring incoming vibration as process with" - + " uid= " + uid + " is background," - + " attrs= " + vib.attrs); - endVibrationLocked(vib, Vibration.Status.IGNORED_BACKGROUND); - return; - } - final long ident = Binder.clearCallingIdentity(); - try { - doCancelVibrateLocked(Vibration.Status.CANCELLED); - startVibrationLocked(vib); - boolean isNextVibration = mNextVibrationThread != null - && vib.equals(mNextVibrationThread.getVibration()); - - if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) { - // Vibration was unexpectedly ignored: add to list for debugging - endVibrationLocked(vib, Vibration.Status.IGNORED); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private boolean hasPermission(String permission) { - return mContext.checkCallingOrSelfPermission(permission) - == PackageManager.PERMISSION_GRANTED; - } - - private static boolean isRepeatingVibration(VibrationEffect effect) { - return effect.getDuration() == Long.MAX_VALUE; - } - - private static <T extends VibrationEffect> T getEffect(Vibration vib) { - return (T) ((CombinedVibrationEffect.Mono) vib.getEffect()).getEffect(); - } - - private void endVibrationLocked(Vibration vib, Vibration.Status status) { - final LinkedList<Vibration.DebugInfo> previousVibrations; - switch (vib.attrs.getUsage()) { - case VibrationAttributes.USAGE_NOTIFICATION: - previousVibrations = mPreviousNotificationVibrations; - break; - case VibrationAttributes.USAGE_RINGTONE: - previousVibrations = mPreviousRingVibrations; - break; - case VibrationAttributes.USAGE_ALARM: - previousVibrations = mPreviousAlarmVibrations; - break; - default: - previousVibrations = mPreviousVibrations; - } - if (previousVibrations.size() > mPreviousVibrationsLimit) { - previousVibrations.removeFirst(); - } - vib.end(status); - previousVibrations.addLast(vib.getDebugInfo()); - } - - private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) { - if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) { - mPreviousExternalVibrations.removeFirst(); - } - vib.end(status); - mPreviousExternalVibrations.addLast(vib.getDebugInfo()); - } - - @Override // Binder call - public void cancelVibrate(IBinder token) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.VIBRATE, - "cancelVibrate"); - - synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.token == token) { - if (DEBUG) { - Slog.d(TAG, "Canceling vibration."); - } - final long ident = Binder.clearCallingIdentity(); - try { - mNextVibrationThread = null; - doCancelVibrateLocked(Vibration.Status.CANCELLED); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - } - - @GuardedBy("mLock") - private void doCancelVibrateLocked(Vibration.Status status) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked"); - try { - if (mThread != null) { - mThread.cancel(); - } - mInputDeviceDelegate.cancelVibrateIfAvailable(); - if (mCurrentExternalVibration != null) { - endVibrationLocked(mCurrentExternalVibration, status); - mCurrentExternalVibration.externalVibration.mute(); - mCurrentExternalVibration = null; - mVibratorController.setExternalControl(false); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @GuardedBy("mLock") - private void startVibrationLocked(final Vibration vib) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); - try { - if (!shouldVibrate(vib)) { - return; - } - applyVibrationIntensityScalingLocked(vib); - startVibrationInnerLocked(vib); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @GuardedBy("mLock") - private void startVibrationInnerLocked(Vibration vib) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked"); - try { - boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( - vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); - if (inputDevicesAvailable) { - endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES); - } else if (mThread == null) { - startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock, - mBatteryStatsService, mVibrationCallbacks)); - } else { - mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock, - mBatteryStatsService, mVibrationCallbacks); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @GuardedBy("mLock") - private void startVibrationThreadLocked(VibrationThread thread) { - Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); - mCurrentVibration = thread.getVibration(); - mThread = thread; - mThread.start(); - } - - /** Scale the vibration effect by the intensity as appropriate based its intent. */ - private void applyVibrationIntensityScalingLocked(Vibration vib) { - vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage())); - } - - private static boolean shouldBypassDnd(VibrationAttributes attrs) { - return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY); - } - - private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) { - int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE, - attrs.getAudioUsage(), uid, packageName); - if (mode == AppOpsManager.MODE_ALLOWED) { - mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName); - } - - if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) { - // If we're just ignoring the vibration op then this is set by DND and we should ignore - // if we're asked to bypass. AppOps won't be able to record this operation, so make - // sure we at least note it in the logs for debugging. - Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid); - mode = AppOpsManager.MODE_ALLOWED; - } - return mode; - } - - private boolean shouldVibrate(Vibration vib) { - if (!mVibrationSettings.shouldVibrateForPowerMode(vib.attrs.getUsage())) { - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_POWER); - return false; - } - - int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage()); - if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_SETTINGS); - return false; - } - - if (!mVibrationSettings.shouldVibrateForRingerMode(vib.attrs.getUsage())) { - if (DEBUG) { - Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_RINGTONE); - return false; - } - - final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs); - if (mode != AppOpsManager.MODE_ALLOWED) { - if (mode == AppOpsManager.MODE_ERRORED) { - // We might be getting calls from within system_server, so we don't actually - // want to throw a SecurityException here. - Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid); - endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS); - } else { - endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS); - } - return false; - } - - return true; - } - - @GuardedBy("mLock") - private void reportFinishVibrationLocked(Vibration.Status status) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked"); - try { - if (mCurrentVibration != null) { - endVibrationLocked(mCurrentVibration, status); - mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid, - mCurrentVibration.opPkg); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); - mCurrentVibration = null; - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @VisibleForTesting - void updateVibrators() { - synchronized (mLock) { - boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators( - mVibrationSettings.shouldVibrateInputDevices()); - - if (mCurrentVibration == null) { - return; - } - - if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode( - mCurrentVibration.attrs.getUsage())) { - // If the state changes out from under us then just reset. - doCancelVibrateLocked(Vibration.Status.CANCELLED); - } - } - } - - private boolean isSystemHapticFeedback(Vibration vib) { - if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) { - return false; - } - return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg); - } - - private void dumpInternal(PrintWriter pw) { - pw.println("Vibrator Service:"); - synchronized (mLock) { - pw.print(" mCurrentVibration="); - if (mCurrentVibration != null) { - pw.println(mCurrentVibration.getDebugInfo().toString()); - } else { - pw.println("null"); - } - pw.print(" mCurrentExternalVibration="); - if (mCurrentExternalVibration != null) { - pw.println(mCurrentExternalVibration.getDebugInfo().toString()); - } else { - pw.println("null"); - } - pw.println(" mVibratorController=" + mVibratorController); - pw.println(" mVibrationSettings=" + mVibrationSettings); - pw.println(); - pw.println(" Previous ring vibrations:"); - for (Vibration.DebugInfo info : mPreviousRingVibrations) { - pw.print(" "); - pw.println(info.toString()); - } - - pw.println(" Previous notification vibrations:"); - for (Vibration.DebugInfo info : mPreviousNotificationVibrations) { - pw.println(" " + info); - } - - pw.println(" Previous alarm vibrations:"); - for (Vibration.DebugInfo info : mPreviousAlarmVibrations) { - pw.println(" " + info); - } - - pw.println(" Previous vibrations:"); - for (Vibration.DebugInfo info : mPreviousVibrations) { - pw.println(" " + info); - } - - pw.println(" Previous external vibrations:"); - for (Vibration.DebugInfo info : mPreviousExternalVibrations) { - pw.println(" " + info); - } - } - } - - private void dumpProto(FileDescriptor fd) { - final ProtoOutputStream proto = new ProtoOutputStream(fd); - - synchronized (mLock) { - if (mCurrentVibration != null) { - mCurrentVibration.getDebugInfo().dumpProto(proto, - VibratorServiceDumpProto.CURRENT_VIBRATION); - } - if (mCurrentExternalVibration != null) { - mCurrentExternalVibration.getDebugInfo().dumpProto(proto, - VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION); - } - proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating()); - proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL, - mVibratorController.isUnderExternalControl()); - mVibrationSettings.dumpProto(proto); - - for (Vibration.DebugInfo info : mPreviousRingVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousNotificationVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousAlarmVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousExternalVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS); - } - } - proto.flush(); - } - - /** Point of injection for test dependencies */ - @VisibleForTesting - static class Injector { - - VibratorController createVibratorController(OnVibrationCompleteListener listener) { - return new VibratorController(/* vibratorId= */ -1, listener); - } - - Handler createHandler(Looper looper) { - return new Handler(looper); - } - - void addService(String name, IBinder service) { - ServiceManager.addService(name, service); - } - } - - BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { - synchronized (mLock) { - // When the system is entering a non-interactive state, we want - // to cancel vibrations in case a misbehaving app has abandoned - // them. However it may happen that the system is currently playing - // haptic feedback as part of the transition. So we don't cancel - // system vibrations. - if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) { - mNextVibrationThread = null; - doCancelVibrateLocked(Vibration.Status.CANCELLED); - } - } - } - } - }; - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - - final long ident = Binder.clearCallingIdentity(); - - boolean isDumpProto = false; - for (String arg : args) { - if (arg.equals("--proto")) { - isDumpProto = true; - } - } - try { - if (isDumpProto) { - dumpProto(fd); - } else { - dumpInternal(pw); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver); - } - - final class ExternalVibratorService extends IExternalVibratorService.Stub { - ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient; - - @Override - public int onExternalVibrationStart(ExternalVibration vib) { - if (!mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { - return IExternalVibratorService.SCALE_MUTE; - } - if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE, - vib.getUid(), -1 /*owningUid*/, true /*exported*/) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() - + " tried to play externally controlled vibration" - + " without VIBRATE permission, ignoring."); - return IExternalVibratorService.SCALE_MUTE; - } - - int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes()); - if (mode != AppOpsManager.MODE_ALLOWED) { - ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); - vibHolder.scale = SCALE_MUTE; - if (mode == AppOpsManager.MODE_ERRORED) { - Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid()); - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS); - } else { - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS); - } - return IExternalVibratorService.SCALE_MUTE; - } - - VibrationThread cancelingVibration = null; - int scale; - synchronized (mLock) { - if (mCurrentExternalVibration != null - && mCurrentExternalVibration.externalVibration.equals(vib)) { - // We are already playing this external vibration, so we can return the same - // scale calculated in the previous call to this method. - return mCurrentExternalVibration.scale; - } - if (mCurrentExternalVibration == null) { - // If we're not under external control right now, then cancel any normal - // vibration that may be playing and ready the vibrator for external control. - mNextVibrationThread = null; - doCancelVibrateLocked(Vibration.Status.CANCELLED); - cancelingVibration = mThread; - } else { - endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); - } - // At this point we either have an externally controlled vibration playing, or - // no vibration playing. Since the interface defines that only one externally - // controlled vibration can play at a time, by returning something other than - // SCALE_MUTE from this function we can be assured that if we are currently - // playing vibration, it will be muted in favor of the new vibration. - // - // Note that this doesn't support multiple concurrent external controls, as we - // would need to mute the old one still if it came from a different controller. - mCurrentExternalVibration = new ExternalVibrationHolder(vib); - mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient(); - vib.linkToDeath(mCurrentExternalDeathRecipient); - mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( - vib.getVibrationAttributes().getUsage()); - scale = mCurrentExternalVibration.scale; - } - if (cancelingVibration != null) { - try { - cancelingVibration.join(); - } catch (InterruptedException e) { - Slog.w("Interrupted while waiting current vibration to be cancelled before " - + "starting external vibration", e); - } - } - if (DEBUG) { - Slog.d(TAG, "Vibrator going under external control."); - } - mVibratorController.setExternalControl(true); - if (DEBUG) { - Slog.e(TAG, "Playing external vibration: " + vib); - } - return scale; - } - - @Override - public void onExternalVibrationStop(ExternalVibration vib) { - synchronized (mLock) { - if (mCurrentExternalVibration != null - && mCurrentExternalVibration.externalVibration.equals(vib)) { - if (DEBUG) { - Slog.e(TAG, "Stopping external vibration" + vib); - } - doCancelExternalVibrateLocked(Vibration.Status.FINISHED); - } - } - } - - private void doCancelExternalVibrateLocked(Vibration.Status status) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked"); - try { - if (mCurrentExternalVibration == null) { - return; - } - endVibrationLocked(mCurrentExternalVibration, status); - mCurrentExternalVibration.externalVibration.unlinkToDeath( - mCurrentExternalDeathRecipient); - mCurrentExternalDeathRecipient = null; - mCurrentExternalVibration = null; - mVibratorController.setExternalControl(false); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient { - public void binderDied() { - synchronized (mLock) { - if (mCurrentExternalVibration != null) { - if (DEBUG) { - Slog.d(TAG, "External vibration finished because binder died"); - } - doCancelExternalVibrateLocked(Vibration.Status.CANCELLED); - } - } - } - } - } - - private final class VibratorShellCommand extends ShellCommand { - - private final IBinder mToken; - - private final class CommonOptions { - public boolean force = false; - public void check(String opt) { - switch (opt) { - case "-f": - force = true; - break; - } - } - } - - private VibratorShellCommand(IBinder token) { - mToken = token; - } - - @Override - public int onCommand(String cmd) { - if ("vibrate".equals(cmd)) { - return runVibrate(); - } else if ("waveform".equals(cmd)) { - return runWaveform(); - } else if ("prebaked".equals(cmd)) { - return runPrebaked(); - } else if ("capabilities".equals(cmd)) { - return runCapabilities(); - } else if ("cancel".equals(cmd)) { - cancelVibrate(mToken); - return 0; - } - return handleDefaultCommands(cmd); - } - - private boolean checkDoNotDisturb(CommonOptions opts) { - if (mVibrationSettings.isInZenMode() && !opts.force) { - try (PrintWriter pw = getOutPrintWriter();) { - pw.print("Ignoring because device is on DND mode "); - return true; - } - } - return false; - } - - private int runVibrate() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate"); - try { - CommonOptions commonOptions = new CommonOptions(); - - String opt; - while ((opt = getNextOption()) != null) { - commonOptions.check(opt); - } - - if (checkDoNotDisturb(commonOptions)) { - return 0; - } - - final long duration = Long.parseLong(getNextArgRequired()); - String description = getNextArg(); - if (description == null) { - description = "Shell command"; - } - - VibrationEffect effect = - VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE); - VibrationAttributes attrs = createVibrationAttributes(commonOptions); - vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command", - mToken); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private int runWaveform() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform"); - try { - String description = "Shell command"; - int repeat = -1; - ArrayList<Integer> amplitudesList = null; - CommonOptions commonOptions = new CommonOptions(); - - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "-d": - description = getNextArgRequired(); - break; - case "-r": - repeat = Integer.parseInt(getNextArgRequired()); - break; - case "-a": - if (amplitudesList == null) { - amplitudesList = new ArrayList<Integer>(); - } - break; - default: - commonOptions.check(opt); - break; - } - } - - if (checkDoNotDisturb(commonOptions)) { - return 0; - } - - ArrayList<Long> timingsList = new ArrayList<Long>(); - - String arg; - while ((arg = getNextArg()) != null) { - if (amplitudesList != null && amplitudesList.size() < timingsList.size()) { - amplitudesList.add(Integer.parseInt(arg)); - } else { - timingsList.add(Long.parseLong(arg)); - } - } - - VibrationEffect effect; - long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray(); - if (amplitudesList == null) { - effect = VibrationEffect.createWaveform(timings, repeat); - } else { - int[] amplitudes = - amplitudesList.stream().mapToInt(Integer::intValue).toArray(); - effect = VibrationEffect.createWaveform(timings, amplitudes, repeat); - } - VibrationAttributes attrs = createVibrationAttributes(commonOptions); - vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command", - mToken); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private int runPrebaked() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked"); - try { - CommonOptions commonOptions = new CommonOptions(); - boolean shouldFallback = false; - - String opt; - while ((opt = getNextOption()) != null) { - if ("-b".equals(opt)) { - shouldFallback = true; - } else { - commonOptions.check(opt); - } - } - - if (checkDoNotDisturb(commonOptions)) { - return 0; - } - - final int id = Integer.parseInt(getNextArgRequired()); - - String description = getNextArg(); - if (description == null) { - description = "Shell command"; - } - - VibrationEffect effect = VibrationEffect.get(id, shouldFallback); - VibrationAttributes attrs = createVibrationAttributes(commonOptions); - vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command", - mToken); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private int runCapabilities() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities"); - try (PrintWriter pw = getOutPrintWriter();) { - pw.println("Vibrator capabilities:"); - if (mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { - pw.println(" Always on effects"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { - pw.println(" Compose effects"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { - pw.println(" Amplitude control"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { - pw.println(" External control"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) { - pw.println(" External amplitude control"); - } - pw.println(""); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) { - final int flags = commonOptions.force - ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY - : 0; - return new VibrationAttributes.Builder() - .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) - // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects. - .setUsage(VibrationAttributes.USAGE_TOUCH) - .build(); - } - - @Override - public void onHelp() { - try (PrintWriter pw = getOutPrintWriter();) { - pw.println("Vibrator commands:"); - pw.println(" help"); - pw.println(" Prints this help text."); - pw.println(""); - pw.println(" vibrate duration [description]"); - pw.println(" Vibrates for duration milliseconds; ignored when device is on "); - pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting "); - pw.println(" will be used to scale amplitude."); - pw.println(" waveform [-d description] [-r index] [-a] duration [amplitude] ..."); - pw.println(" Vibrates for durations and amplitudes in list; ignored when "); - pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength "); - pw.println(" user setting will be used to scale amplitude."); - pw.println(" If -r is provided, the waveform loops back to the specified"); - pw.println(" index (e.g. 0 loops from the beginning)"); - pw.println(" If -a is provided, the command accepts duration-amplitude pairs;"); - pw.println(" otherwise, it accepts durations only and alternates off/on"); - pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255."); - pw.println(" prebaked [-b] effect-id [description]"); - pw.println(" Vibrates with prebaked effect; ignored when device is on DND "); - pw.println(" (Do Not Disturb) mode; touch feedback strength user setting "); - pw.println(" will be used to scale amplitude."); - pw.println(" If -b is provided, the prebaked fallback effect will be played if"); - pw.println(" the device doesn't support the given effect-id."); - pw.println(" capabilities"); - pw.println(" Prints capabilities of this device."); - pw.println(" cancel"); - pw.println(" Cancels any active vibration"); - pw.println("Common Options:"); - pw.println(" -f - Force. Ignore Do Not Disturb setting."); - pw.println(""); - } - } - } -} 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/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index c6947c2d9525..b9943897a486 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -15,8 +15,6 @@ */ package com.android.server.am; -import static com.android.internal.power.MeasuredEnergyArray.SUBSYSTEM_DISPLAY; - import android.annotation.Nullable; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; @@ -39,13 +37,12 @@ import android.telephony.ModemActivityInfo; import android.telephony.TelephonyManager; import android.util.IntArray; import android.util.Slog; -import android.util.SparseIntArray; +import android.util.SparseArray; import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BatteryStatsImpl; -import com.android.internal.power.MeasuredEnergyArray; import com.android.internal.power.MeasuredEnergyStats; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.function.pooled.PooledLambda; @@ -148,13 +145,13 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { private WifiActivityEnergyInfo mLastWifiInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0); - /** Maps the EnergyConsumer id to it's corresponding {@link MeasuredEnergySubsystem} */ - @GuardedBy("mWorkerLock") - private @Nullable SparseIntArray mEnergyConsumerToSubsystemMap = null; - - /** Maps a {@link MeasuredEnergySubsystem} to it's corresponding EnergyConsumer id */ + /** + * 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) @GuardedBy("mWorkerLock") - private @Nullable SparseIntArray mSubsystemToEnergyConsumerMap = null; + private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null; /** Snapshot of measured energies, or null if no measured energies are supported. */ @GuardedBy("mWorkerLock") @@ -204,18 +201,26 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { mWifiManager = wm; mTelephony = tm; mPowerStatsInternal = psi; + + boolean[] supportedStdBuckets = null; + int numCustomBuckets = 0; if (mPowerStatsInternal != null) { - populateEnergyConsumerSubsystemMapsLocked(); - final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData(); - mMeasuredEnergySnapshot = initialMeasuredEnergies == null - ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies); - final boolean[] supportedStdBuckets - = getSupportedEnergyBuckets(initialMeasuredEnergies); - final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies - synchronized (mStats) { - mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets); + final SparseArray<EnergyConsumer> idToConsumer + = populateEnergyConsumerSubsystemMapsLocked(); + if (idToConsumer != null) { + mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer); + final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData(); + // According to spec, initialEcrs will include 0s for consumers that haven't + // used any energy yet, as long as they are supported; however, attributed uid + // energies will be absent if their energy is 0. + mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs); + numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals(); + supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer); } } + synchronized (mStats) { + mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets); + } } } @@ -568,7 +573,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } catch (ExecutionException e) { Slog.w(TAG, "exception reading modem stats: " + e.getCause()); } - final SparseLongArray energyDeltas = mMeasuredEnergySnapshot == null ? null : + + final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas = + mMeasuredEnergySnapshot == null ? null : mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags)); final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -576,6 +583,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { final long elapsedRealtimeUs = elapsedRealtime * 1000; final long uptimeUs = uptime * 1000; + // Now that we have finally received all the data, we can tell mStats about it. synchronized (mStats) { mStats.addHistoryEventLocked( elapsedRealtime, @@ -601,10 +609,21 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } // Inform mStats about each applicable measured energy. - if (energyDeltas != null) { - final long displayEnergy = energyDeltas.get(SUBSYSTEM_DISPLAY, 0L); - // Always pass in what BatteryExternalStatsWorker thinks screenState is. - mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime); + if (measuredEnergyDeltas != null) { + final long displayEnergy = measuredEnergyDeltas.displayEnergyUJ; + if (displayEnergy != MeasuredEnergySnapshot.UNAVAILABLE) { + // If updating, pass in what BatteryExternalStatsWorker thinks screenState is. + mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime); + } + } + // Inform mStats about each applicable custom energy bucket. + if (measuredEnergyDeltas != null && measuredEnergyDeltas.otherTotalEnergyUJ != null) { + // Iterate over the custom (EnergyConsumerType.OTHER) ordinals. + for (int ord = 0; ord < measuredEnergyDeltas.otherTotalEnergyUJ.length; ord++) { + long totalEnergy = measuredEnergyDeltas.otherTotalEnergyUJ[ord]; + SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidEnergiesUJ[ord]; + mStats.updateCustomMeasuredEnergyDataLocked(ord, totalEnergy, uidEnergies); + } } if (bluetoothInfo != null) { @@ -621,7 +640,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { if (wifiInfo != null) { if (wifiInfo.isValid()) { - // TODO: wifiEnergyDelta = energyDeltas.get(MeasuredEnergyArray.SUBSYSTEM_WIFI, 0L); + // TODO: wifiEnergyDelta = measuredEnergyDeltas.consumerTypeEnergyUJ + // .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE) mStats.updateWifiState(extractDeltaLocked(wifiInfo) /*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime); } else { @@ -740,21 +760,23 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } /** - * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to + * Map the {@link EnergyConsumerType}s in the given energyArray to * their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}s. * Does not include custom energy buckets (which are always, by definition, supported). * * @return array with true for index i if standard energy bucket i is supported. */ - private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) { - if (energyArray == null) { + private static @Nullable boolean[] getSupportedEnergyBuckets( + SparseArray<EnergyConsumer> idToConsumer) { + if (idToConsumer == null) { return null; } final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS]; - final int size = energyArray.size(); - for (int energyIdx = 0; energyIdx < size; energyIdx++) { - switch (energyArray.getSubsystem(energyIdx)) { - case MeasuredEnergyArray.SUBSYSTEM_DISPLAY: + final int size = idToConsumer.size(); + for (int idx = 0; idx < size; idx++) { + final EnergyConsumer consumer = idToConsumer.valueAt(idx); + switch (consumer.type) { + case EnergyConsumerType.DISPLAY: buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON] = true; buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE] = true; buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER] = true; @@ -764,71 +786,22 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { return buckets; } - /** - * Get a {@link MeasuredEnergyArray} with the latest - * {@link MeasuredEnergyArray.MeasuredEnergySubsystem} energy usage since boot. - * - * TODO(b/176988041): Replace {@link MeasuredEnergyArray} usage with {@link - * EnergyConsumerResult}[] - */ + /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */ @GuardedBy("mWorkerLock") - @VisibleForTesting - public @Nullable MeasuredEnergyArray getEnergyConsumptionData() { - final EnergyConsumerResult[] results; + private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() { try { - results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0]) + return mPowerStatsInternal.getEnergyConsumedAsync(new int[0]) .get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } catch (Exception e) { Slog.e(TAG, "Failed to getEnergyConsumedAsync", e); return null; } - if (results == null) return null; - final int size = results.length; - final int[] subsystems = new int[size]; - final long[] energyUJ = new long[size]; - - int count = 0; - for (int i = 0; i < size; i++) { - final EnergyConsumerResult consumer = results[i]; - final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id, - MeasuredEnergyArray.SUBSYSTEM_UNKNOWN); - if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue; - subsystems[count] = subsystem; - energyUJ[count] = consumer.energyUWs; - count++; - } - final int arraySize = count; - return new MeasuredEnergyArray() { - @Override - public int getSubsystem(int index) { - if (index >= size()) { - throw new IllegalArgumentException( - "Out of bounds subsystem index! index : " + index + ", size : " - + size()); - } - return subsystems[index]; - } - - @Override - public long getEnergy(int index) { - if (index >= size()) { - throw new IllegalArgumentException( - "Out of bounds subsystem index! index : " + index + ", size : " - + size()); - } - return energyUJ[index]; - } - - @Override - public int size() { - return arraySize; - } - }; } - /** Fetch MeasuredEnergyArray for supported subsystems based on the given updateFlags. */ + /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */ @GuardedBy("mWorkerLock") - private @Nullable MeasuredEnergyArray getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) { + private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) + { if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null; if (flags == UPDATE_ALL) { @@ -836,12 +809,13 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { return getEnergyConsumptionData(); } - final List<Integer> energySubsystems = new ArrayList<>(); + final List<Integer> energyConsumerIds = new ArrayList<>(); if ((flags & UPDATE_DISPLAY) != 0) { - addEnergyConsumerIdLocked(energySubsystems, SUBSYSTEM_DISPLAY); + addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY); } // TODO: Wifi, Bluetooth, etc., go here - if (energySubsystems.isEmpty()) { + + if (energyConsumerIds.isEmpty()) { return null; } // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray() @@ -849,59 +823,48 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } @GuardedBy("mWorkerLock") - private void addEnergyConsumerIdLocked(List<Integer> energyConsumerIds, - @MeasuredEnergyArray.MeasuredEnergySubsystem int consumerId) { - if (mMeasuredEnergySnapshot.hasSubsystem(consumerId)) { - energyConsumerIds.add(consumerId); - } + private void addEnergyConsumerIdLocked( + List<Integer> energyConsumerIds, @EnergyConsumerType int type) { + final int consumerId = 0; // TODO: Use mEnergyConsumerTypeToIdMap to get this + energyConsumerIds.add(consumerId); } + /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */ @GuardedBy("mWorkerLock") - private void populateEnergyConsumerSubsystemMapsLocked() { + private @Nullable SparseArray<EnergyConsumer> populateEnergyConsumerSubsystemMapsLocked() { if (mPowerStatsInternal == null) { - // PowerStatsInternal unavailable, don't bother populating maps. - mEnergyConsumerToSubsystemMap = null; - mSubsystemToEnergyConsumerMap = null; - return; + return null; } final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo(); - if (energyConsumers == null) { - // EnergyConsumer data unavailable, don't bother populating maps. - mEnergyConsumerToSubsystemMap = null; - mSubsystemToEnergyConsumerMap = null; - return; - } - - final int length = energyConsumers.length; - if (length == 0) { - // EnergyConsumer array empty, don't bother populating maps. - mEnergyConsumerToSubsystemMap = null; - mSubsystemToEnergyConsumerMap = null; - return; - } else { - mEnergyConsumerToSubsystemMap = new SparseIntArray(length); - mSubsystemToEnergyConsumerMap = new SparseIntArray(length); + if (energyConsumers == null || energyConsumers.length == 0) { + return null; } + // TODO: Initialize typeToIds + // Maps type -> {ids} (1:n map, since multiple ids might have the same type) + // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>(); + + // Maps id -> EnergyConsumer (1:1 map) + final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length); + // Add all expected EnergyConsumers to the maps - for (int i = 0; i < length; i++) { - final EnergyConsumer consumer = energyConsumers[i]; - switch (consumer.type) { - case EnergyConsumerType.DISPLAY: - if (consumer.ordinal == 0) { - mEnergyConsumerToSubsystemMap.put(consumer.id, - MeasuredEnergyArray.SUBSYSTEM_DISPLAY); - mSubsystemToEnergyConsumerMap.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, - consumer.id); - } else { - Slog.w(TAG, "Unexpected ordinal (" + consumer.ordinal - + ") for EnergyConsumerType.DISPLAY"); - } - break; - default: - Slog.w(TAG, "Unexpected EnergyConsumerType (" + consumer.type + ")"); + for (final EnergyConsumer consumer : energyConsumers) { + // Check for inappropriate ordinals + if (consumer.ordinal != 0) { + switch (consumer.type) { + case EnergyConsumerType.OTHER: + case EnergyConsumerType.CPU_CLUSTER: + break; + default: + Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal " + + consumer.ordinal + " for type " + consumer.type); + continue; // Ignore this consumer + } } - + idToConsumer.put(consumer.id, consumer); + // TODO: Also populate typeToIds map } + // TODO: Store typeToIds in mEnergyConsumerTypeToIdMap. + return idToConsumer; } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 7e65434b8189..8b6fabd7faff 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -967,6 +967,12 @@ public final class BroadcastQueue { } } + static String broadcastDescription(BroadcastRecord r, ComponentName component) { + return r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ") to " + component.flattenToShortString(); + } + final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { BroadcastRecord r; @@ -1349,14 +1355,18 @@ public final class BroadcastQueue { < brOptions.getMinManifestReceiverApiLevel() || info.activityInfo.applicationInfo.targetSdkVersion > brOptions.getMaxManifestReceiverApiLevel())) { + Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo + + " targets " + info.activityInfo.applicationInfo.targetSdkVersion + + " but delivery restricted to [" + + brOptions.getMinManifestReceiverApiLevel() + ", " + + brOptions.getMaxManifestReceiverApiLevel() + + "] broadcasting " + broadcastDescription(r, component)); skip = true; } if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid, component.getPackageName(), info.activityInfo.applicationInfo.uid)) { Slog.w(TAG, "Association not allowed: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ") to " + component.flattenToShortString()); + + broadcastDescription(r, component)); skip = true; } if (!skip) { @@ -1364,9 +1374,7 @@ public final class BroadcastQueue { r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid); if (skip) { Slog.w(TAG, "Firewall blocked: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ") to " + component.flattenToShortString()); + + broadcastDescription(r, component)); } } int perm = mService.checkComponentPermission(info.activityInfo.permission, @@ -1375,18 +1383,12 @@ public final class BroadcastQueue { if (!skip && perm != PackageManager.PERMISSION_GRANTED) { if (!info.activityInfo.exported) { Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " is not exported from uid " + info.activityInfo.applicationInfo.uid - + " due to receiver " + component.flattenToShortString()); + + broadcastDescription(r, component) + + " is not exported from uid " + info.activityInfo.applicationInfo.uid); } else { Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " requires " + info.activityInfo.permission - + " due to receiver " + component.flattenToShortString()); + + broadcastDescription(r, component) + + " requires " + info.activityInfo.permission); } skip = true; } else if (!skip && info.activityInfo.permission != null) { @@ -1396,13 +1398,9 @@ public final class BroadcastQueue { "Broadcast delivered to " + info.activityInfo.name) != AppOpsManager.MODE_ALLOWED) { Slog.w(TAG, "Appop Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" - + r.callingPid + ", uid=" + r.callingUid + ")" + + broadcastDescription(r, component) + " requires appop " + AppOpsManager.permissionToOp( - info.activityInfo.permission) - + " due to registered receiver " - + component.flattenToShortString()); + info.activityInfo.permission)); skip = true; } } @@ -1520,7 +1518,7 @@ public final class BroadcastQueue { + info.activityInfo.packageName, e); } if (!isAvailable) { - if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, + Slog.w(TAG_BROADCAST, "Skipping delivery to " + info.activityInfo.packageName + " / " + info.activityInfo.applicationInfo.uid + " : package no longer available"); @@ -1536,6 +1534,9 @@ public final class BroadcastQueue { if (!requestStartTargetPermissionsReviewIfNeededLocked(r, info.activityInfo.packageName, UserHandle.getUserId( info.activityInfo.applicationInfo.uid))) { + Slog.w(TAG_BROADCAST, + "Skipping delivery: permission review required for " + + broadcastDescription(r, component)); skip = true; } } diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java index b915c0ce44f3..9e0aa32a0b9e 100644 --- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java +++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java @@ -17,132 +17,260 @@ package com.android.server.am; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerAttribution; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.power.MeasuredEnergyArray; -import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem; import java.io.PrintWriter; -import java.util.Arrays; /** - * Keeps snapshots of data from previously pulled MeasuredEnergyArrays. + * Keeps snapshots of data from previously pulled EnergyConsumerResults. */ @VisibleForTesting public class MeasuredEnergySnapshot { private static final String TAG = "MeasuredEnergySnapshot"; - private static final long UNAVAILABLE = -1; + public static final long UNAVAILABLE = -1L; + + /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */ + private final SparseArray<EnergyConsumer> mEnergyConsumers; + + /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */ + private final int mNumOtherOrdinals; /** - * Energy snapshots from the last time each {@link MeasuredEnergySubsystem} was updated. + * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time + * each {@link EnergyConsumer} was updated. * - * Note that the snapshots for different subsystems may have been taken at different times. + * Note that the snapshots for different ids may have been taken at different times. + * Note that energies for all existing ids are stored here, including each ordinal of type + * {@link EnergyConsumerType#OTHER} (tracking their total energy usage). * - * A snapshot is {@link #UNAVAILABLE} if the subsystem has never been updated (ie. unsupported). + * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}). */ - private final long[] mMeasuredEnergySnapshots; + private final SparseLongArray mMeasuredEnergySnapshots; /** - * Constructor that initializes to the given energyArray; - * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE. + * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type + * {@link EnergyConsumerType#OTHER} was updated. + * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each + * uid to an energy (UJ). That is, + * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer. + * + * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable). + * If an id is present but a uid is not present, that uid's energy is 0. */ - public MeasuredEnergySnapshot(MeasuredEnergyArray initialEnergyArray) { - this(MeasuredEnergyArray.NUMBER_SUBSYSTEMS, initialEnergyArray); - } + private final SparseArray<SparseLongArray> mAttributionSnapshots; /** - * Constructor (for testing) that initializes to the given energyArray and numSubsystems; - * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE. + * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers + * exist and what their details are. */ - @VisibleForTesting - MeasuredEnergySnapshot(int numSubsystems, MeasuredEnergyArray initialEnergyArray) { - if (initialEnergyArray.size() > numSubsystems) { - throw new IllegalArgumentException("Energy array contains " + initialEnergyArray.size() - + " subsystems, which exceeds the maximum allowed of " + numSubsystems); - } - mMeasuredEnergySnapshots = new long[numSubsystems]; - Arrays.fill(mMeasuredEnergySnapshots, UNAVAILABLE); - fillGivenSubsystems(initialEnergyArray); + MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) { + mEnergyConsumers = idToConsumerMap; + mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size()); + + mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap); + mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals); } /** - * For the subsystems present in energyArray, overwrites mMeasuredEnergySnapshots with their - * energy values from energyArray. + * Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of + * custom energy buckets supported by the device. */ - private void fillGivenSubsystems(MeasuredEnergyArray energyArray) { - final int size = energyArray.size(); - for (int i = 0; i < size; i++) { - final int subsystem = energyArray.getSubsystem(i); - mMeasuredEnergySnapshots[subsystem] = energyArray.getEnergy(i); - } + public int getNumOtherOrdinals() { + return mNumOtherOrdinals; + } + + /** Class for returning measured energy delta data. */ + static class MeasuredEnergyDeltaData { + /** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */ + public long displayEnergyUJ = UNAVAILABLE; + + /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */ + public @Nullable long[] otherTotalEnergyUJ = null; + + /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */ + public @Nullable SparseLongArray[] otherUidEnergiesUJ = null; } /** * Update with the some freshly measured energies and return the difference (delta) * between the previously stored values and the passed-in values. * - * @param energyArray measured energy array for some (possibly not all) subsystems. + * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s. + * Consumers that are not present are ignored (they are *not* treated as 0). * - * @return a map from the updated subsystems to their corresponding energy deltas. - * Subsystems not present in energyArray will not appear. - * Subsystems with no difference in energy will not appear. - * Returns null, if energyArray is null. + * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to + * their corresponding energy deltas. + * Fields with no interesting data (consumers not present in ecrs or with no energy + * difference) will generally be left as their default values. + * otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of + * length {@link #getNumOtherOrdinals()}. + * Returns null, if ecrs is null or empty. */ - public @Nullable SparseLongArray updateAndGetDelta(MeasuredEnergyArray energyArray) { - if (energyArray == null) { + public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) { + if (ecrs == null || ecrs.length == 0) { return null; } - final SparseLongArray delta = new SparseLongArray(); - final int size = energyArray.size(); - for (int i = 0; i < size; i++) { - final int updatedSubsystem = energyArray.getSubsystem(i); - final long newEnergyUJ = energyArray.getEnergy(i); - final long oldEnergyUJ = mMeasuredEnergySnapshots[updatedSubsystem]; - - // If this is the first valid energy, there is no delta to take. - if (oldEnergyUJ < 0) continue; + final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData(); + + for (final EnergyConsumerResult ecr : ecrs) { + // Extract the new energy data for the current consumer. + final int consumerId = ecr.id; + final long newEnergyUJ = ecr.energyUWs; + final EnergyConsumerAttribution[] newAttributions = ecr.attribution; + + // Look up the static information about this consumer. + final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null); + if (consumer == null) { + Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId); + continue; + } + final int type = consumer.type; + final int ordinal = consumer.ordinal; + + // Look up, and update, the old energy information about this consumer. + final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE); + mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ); + final SparseLongArray otherUidEnergies + = updateAndGetDeltaForTypeOther(consumer, newAttributions); + + // Everything is fully done being updated. We now calculate the delta for returning. + + // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0 + // there's no attribution either. Technically that isn't enforced at the HAL, but we + // can't really trust data like that anyway. + + if (oldEnergyUJ < 0) continue; // Generally happens only on initialization. + if (newEnergyUJ == oldEnergyUJ) continue; final long deltaUJ = newEnergyUJ - oldEnergyUJ; - if (deltaUJ == 0) continue; if (deltaUJ < 0) { - Slog.e(TAG, "For subsystem " + updatedSubsystem + ", new energy (" + newEnergyUJ - + ") is less than old energy (" + oldEnergyUJ + "). Skipping. "); + Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ + + ") < old energy (" + oldEnergyUJ + "). Skipping. "); continue; } - delta.put(updatedSubsystem, deltaUJ); - } - fillGivenSubsystems(energyArray); + switch (type) { + case EnergyConsumerType.DISPLAY: + output.displayEnergyUJ = deltaUJ; + break; + case EnergyConsumerType.OTHER: + if (output.otherTotalEnergyUJ == null) { + output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()]; + output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()]; + } + output.otherTotalEnergyUJ[ordinal] = deltaUJ; + output.otherUidEnergiesUJ[ordinal] = otherUidEnergies; + break; + default: + Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type); - return delta; + } + } + return output; } /** - * Check if a subsystem's measured energy is available. - * @param subsystem which subsystem. - * @return true if subsystem is available. + * For a consumer of type {@link EnergyConsumerType#OTHER}, updates + * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the + * difference (delta) between the previously stored values and the passed-in values. + * + * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}. + * @param newAttributions Record of uids and their new energyUJ values. + * Any uid not present is treated as having energy 0. + * If null or empty, all uids are treated as having energy 0. + * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this + * consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta. + * Returns null if no delta available to calculate. */ - public boolean hasSubsystem(@MeasuredEnergySubsystem int subsystem) { - return mMeasuredEnergySnapshots[subsystem] != UNAVAILABLE; + private @Nullable SparseLongArray updateAndGetDeltaForTypeOther( + @NonNull EnergyConsumer consumerInfo, + @Nullable EnergyConsumerAttribution[] newAttributions) { + + if (consumerInfo.type != EnergyConsumerType.OTHER) { + return null; + } + if (newAttributions == null) { + // Treat null as empty (i.e. all uids have 0 energy). + newAttributions = new EnergyConsumerAttribution[0]; + } + + // SparseLongArray mapping uid -> energyUJ (for this particular consumerId) + SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null); + + // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return. + if (uidOldEnergyMap == null) { + uidOldEnergyMap = new SparseLongArray(newAttributions.length); + mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap); + for (EnergyConsumerAttribution newAttribution : newAttributions) { + uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs); + } + return null; + } + + // Map uid -> energyDelta. No initial capacity since many deltas might be 0. + final SparseLongArray uidEnergyDeltas = new SparseLongArray(); + + for (EnergyConsumerAttribution newAttribution : newAttributions) { + final int uid = newAttribution.uid; + final long newEnergyUJ = newAttribution.energyUWs; + // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy. + final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L); + uidOldEnergyMap.put(uid, newEnergyUJ); + + // Everything is fully done being updated. We now calculate the delta for returning. + if (oldEnergyUJ < 0) continue; + if (newEnergyUJ == oldEnergyUJ) continue; + final long deltaUJ = newEnergyUJ - oldEnergyUJ; + if (deltaUJ < 0) { + Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ + + ") but old energy (" + oldEnergyUJ + "). Skipping. "); + continue; + } + uidEnergyDeltas.put(uid, deltaUJ); + } + return uidEnergyDeltas; } /** Dump debug data. */ public void dump(PrintWriter pw) { - pw.println("Measured energy snapshot (microjoules):"); - pw.print(" "); - for (int i = 0; i < MeasuredEnergyArray.NUMBER_SUBSYSTEMS; i++) { - final long energyUJ = mMeasuredEnergySnapshots[i]; - if (energyUJ == UNAVAILABLE) continue; - pw.print(MeasuredEnergyArray.SUBSYSTEM_NAMES[i]); - pw.print(" : "); - pw.print(energyUJ); - if (i != MeasuredEnergyArray.NUMBER_SUBSYSTEMS - 1) { - pw.print(", "); - } + pw.println("Measured energy snapshot"); + pw.println("List of EnergyConsumers:"); + for (int i = 0; i < mEnergyConsumers.size(); i++) { + final int id = mEnergyConsumers.keyAt(i); + final EnergyConsumer consumer = mEnergyConsumers.valueAt(i); + pw.println(String.format(" Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id, + consumer.id, consumer.ordinal, consumer.type, consumer.name)); + } + pw.println("Map of consumerIds to energy (in microjoules):"); + for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) { + final int id = mMeasuredEnergySnapshots.keyAt(i); + final long energyUJ = mMeasuredEnergySnapshots.valueAt(i); + pw.println(String.format(" Consumer %d has energy %d uJ}", id, energyUJ)); } + pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:"); + pw.println(" " + mAttributionSnapshots); pw.println(); } + + /** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */ + private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) { + if (idToConsumer == null) return 0; + int numOtherOrdinals = 0; + final int size = idToConsumer.size(); + for (int idx = 0; idx < size; idx++) { + final EnergyConsumer consumer = idToConsumer.valueAt(idx); + if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++; + } + return numOtherOrdinals; + } } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 10fe1e1d0684..c92abd48fdc2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -3131,11 +3131,6 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_ERRORED; } final Op op = getOpLocked(ops, code, uid, true); - if (isOpRestrictedLocked(uid, code, packageName, bypass)) { - scheduleOpNotedIfNeededLocked(code, uid, packageName, flags, - AppOpsManager.MODE_IGNORED); - return AppOpsManager.MODE_IGNORED; - } final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); if (attributedOp.isRunning()) { Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code " @@ -3145,6 +3140,12 @@ public class AppOpsService extends IAppOpsService.Stub { final int switchCode = AppOpsManager.opToSwitch(code); final UidState uidState = ops.uidState; + if (isOpRestrictedLocked(uid, code, packageName, bypass)) { + attributedOp.rejected(uidState.state, flags); + scheduleOpNotedIfNeededLocked(code, uid, packageName, flags, + AppOpsManager.MODE_IGNORED); + return AppOpsManager.MODE_IGNORED; + } // If there is a non-default per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) { diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index c9560437799e..f37fbf59af39 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; + } } 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 baeb3fdda67d..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() { @@ -496,6 +490,11 @@ 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 1d57073d6516..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,92 +17,119 @@ 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.Binder; -import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; /** * Test HAL that provides only no-ops. */ public class TestHal extends IFace.Stub { + private static final String TAG = "face.aidl.TestHal"; @Override public SensorProps[] getSensorProps() { + Slog.w(TAG, "getSensorProps"); return new SensorProps[0]; } @Override public ISession createSession(int sensorId, int userId, ISessionCallback cb) { - return new ISession() { - @Override - public void generateChallenge(int cookie, int timeoutSec) { + Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId); + return new ISession.Stub() { + @Override + 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) { - return null; + Slog.w(TAG, "enroll, cookie: " + cookie); + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal authenticate(int cookie, long operationId) { - return null; + Slog.w(TAG, "authenticate, cookie: " + cookie); + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal detectInteraction(int cookie) { - return null; + Slog.w(TAG, "detectInteraction, cookie: " + cookie); + 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) { - - } - - @Override - public void invalidateAuthenticatorId(int cookie) { - + public void getAuthenticatorId(int cookie) throws RemoteException { + Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie); + cb.onAuthenticatorIdRetrieved(0L); } @Override - public void resetLockout(int cookie, HardwareAuthToken hat) { - + public void invalidateAuthenticatorId(int cookie) throws RemoteException { + Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie); + cb.onAuthenticatorIdInvalidated(0L); } @Override - public IBinder asBinder() { - return new Binder(); + 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 bab1114dc70d..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 @@ -25,10 +25,12 @@ import android.hardware.biometrics.face.V1_0.Status; import android.hardware.biometrics.face.V1_1.IBiometricsFace; import android.os.NativeHandle; import android.os.RemoteException; +import android.util.Slog; import java.util.ArrayList; public class TestHal extends IBiometricsFace.Stub { + private static final String TAG = "face.hidl.TestHal"; @Nullable private IBiometricsFaceClientCallback mCallback; @@ -47,6 +49,7 @@ public class TestHal extends IBiometricsFace.Stub { @Override public OptionalUint64 generateChallenge(int challengeTimeoutSec) { + Slog.w(TAG, "generateChallenge"); final OptionalUint64 result = new OptionalUint64(); result.status = Status.OK; result.value = 0; @@ -55,6 +58,7 @@ public class TestHal extends IBiometricsFace.Stub { @Override public int enroll(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures) { + Slog.w(TAG, "enroll"); return 0; } @@ -94,17 +98,23 @@ 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; } @Override public int remove(int faceId) { + Slog.w(TAG, "remove"); return 0; } @Override public int authenticate(long operationId) { + Slog.w(TAG, "authenticate"); return 0; } @@ -115,6 +125,7 @@ public class TestHal extends IBiometricsFace.Stub { @Override public int resetLockout(ArrayList<Byte> hat) { + Slog.w(TAG, "resetLockout"); 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 7e4ee9e77ab2..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,6 +470,11 @@ 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/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java index a31bcdcaf4e6..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,94 +17,120 @@ 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.Binder; -import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; /** * Test HAL that provides only provides no-ops. */ public class TestHal extends IFingerprint.Stub { + private static final String TAG = "fingerprint.aidl.TestHal"; + @Override public SensorProps[] getSensorProps() { + Slog.w(TAG, "getSensorProps"); return new SensorProps[0]; } @Override public ISession createSession(int sensorId, int userId, ISessionCallback cb) { - return new ISession() { - @Override - public void generateChallenge(int cookie, int timeoutSec) { + Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId); + return new ISession.Stub() { + @Override + 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) { - return null; + Slog.w(TAG, "enroll, cookie: " + cookie); + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal authenticate(int cookie, long operationId) { - return null; + Slog.w(TAG, "authenticate, cookie: " + cookie); + return new ICancellationSignal.Stub() { + @Override + public void cancel() throws RemoteException { + cb.onError(Error.CANCELED, 0 /* vendorCode */); + } + }; } @Override public ICancellationSignal detectInteraction(int cookie) { - return null; + Slog.w(TAG, "detectInteraction, cookie: " + cookie); + 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 public void onPointerDown(int pointerId, int x, int y, float minor, float major) { - + Slog.w(TAG, "onPointerDown"); } @Override public void onPointerUp(int pointerId) { - + Slog.w(TAG, "onPointerUp"); } @Override public void onUiReady() { - - } - - @Override - public IBinder asBinder() { - return new Binder(); + Slog.w(TAG, "onUiReady"); } }; } 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 b7aec0ed2731..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 @@ -21,11 +21,14 @@ import android.hardware.biometrics.fingerprint.V2_1.FingerprintError; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback; import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint; import android.os.RemoteException; +import android.util.Slog; /** * Test HAL that provides only provides no-ops. */ public class TestHal extends IBiometricsFingerprint.Stub { + private static final String TAG = "fingerprint.hidl.TestHal"; + @Nullable private IBiometricsFingerprintClientCallback mCallback; @@ -57,6 +60,7 @@ public class TestHal extends IBiometricsFingerprint.Stub { @Override public int enroll(byte[] hat, int gid, int timeoutSec) { + Slog.w(TAG, "enroll"); return 0; } @@ -79,12 +83,18 @@ 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; } @Override public int remove(int gid, int fid) { + Slog.w(TAG, "Remove"); return 0; } @@ -95,6 +105,7 @@ public class TestHal extends IBiometricsFingerprint.Stub { @Override public int authenticate(long operationId, int gid) { + Slog.w(TAG, "Authenticate"); return 0; } }
\ No newline at end of file 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/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index fa063b223250..225da7ad87b3 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -42,8 +42,8 @@ import android.util.MathUtils; import android.util.Slog; import android.util.TimeUtils; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.server.EventLogTags; import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 4832e46be8a8..a62f67a743ad 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -28,8 +28,8 @@ import android.util.Pair; import android.util.Slog; import android.util.Spline; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.Preconditions; import com.android.server.display.utils.Plog; diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java new file mode 100644 index 000000000000..d4556ed5f9fa --- /dev/null +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -0,0 +1,121 @@ +/* + * 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.display; + +import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.text.TextUtils; +import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.SparseArray; +import android.view.DisplayAddress; + +import com.android.server.display.layout.Layout; + +import java.util.Arrays; + +/** + * Mapping from device states into {@link Layout}s. This allows us to map device + * states into specific layouts for the connected displays; particularly useful for + * foldable and multi-display devices where the default display and which displays are ON can + * change depending on the state of the device. + */ +class DeviceStateToLayoutMap { + private static final String TAG = "DeviceStateToLayoutMap"; + + public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE; + + // TODO - b/168208162 - Remove these when we check in static definitions for layouts + public static final int STATE_FOLDED = 100; + public static final int STATE_UNFOLDED = 101; + + private final SparseArray<Layout> mLayoutMap = new SparseArray<>(); + + DeviceStateToLayoutMap(Context context) { + mLayoutMap.append(STATE_DEFAULT, new Layout()); + loadFoldedDisplayConfig(context); + } + + public void dumpLocked(IndentingPrintWriter ipw) { + ipw.println("DeviceStateToLayoutMap:"); + ipw.increaseIndent(); + + ipw.println("Registered Layouts:"); + for (int i = 0; i < mLayoutMap.size(); i++) { + ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i)); + } + } + + Layout get(int state) { + Layout layout = mLayoutMap.get(state); + if (layout == null) { + layout = mLayoutMap.get(STATE_DEFAULT); + } + return layout; + } + + private Layout create(int state) { + if (mLayoutMap.contains(state)) { + Slog.e(TAG, "Attempted to create a second layout for state " + state); + return null; + } + + final Layout layout = new Layout(); + mLayoutMap.append(state, layout); + return layout; + } + + private void loadFoldedDisplayConfig(Context context) { + final String[] strDisplayIds = context.getResources().getStringArray( + com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds); + + if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0]) + || TextUtils.isEmpty(strDisplayIds[1])) { + Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds) + + "]"); + return; + } + + final long[] displayIds; + try { + displayIds = new long[] { + Long.parseLong(strDisplayIds[0]), + Long.parseLong(strDisplayIds[1]) + }; + } catch (NumberFormatException nfe) { + Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds)); + return; + } + + final int[] foldedDeviceStates = context.getResources().getIntArray( + com.android.internal.R.array.config_foldedDeviceStates); + // Only add folded states if folded state config is not empty + if (foldedDeviceStates.length == 0) { + return; + } + + // Create the folded state layout + final Layout foldedLayout = create(STATE_FOLDED); + foldedLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/); + + // Create the unfolded state layout + final Layout unfoldedLayout = create(STATE_UNFOLDED); + unfoldedLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/); + } +} diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index d6872217eab6..1b25427adf71 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -23,8 +23,8 @@ import android.os.PowerManager; import android.util.Slog; import android.view.DisplayAddress; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.R; +import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index bf16a6d5efb9..501533d535d3 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -26,7 +26,7 @@ import android.view.DisplayEventReceiver; import android.view.RoundedCorners; import android.view.Surface; -import com.android.internal.BrightnessSynchronizer; +import com.android.internal.display.BrightnessSynchronizer; import java.util.Arrays; import java.util.Objects; diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java index 5c0fceb9b795..57f44864d2c0 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java +++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.annotation.NonNull; import android.util.Slog; import android.view.Display; +import android.view.DisplayAddress; import com.android.internal.annotations.GuardedBy; import com.android.server.display.DisplayManagerService.SyncRoot; @@ -112,12 +113,11 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener { } } - public DisplayDevice getByIdLocked(@NonNull String uniqueId) { - final int count = mDisplayDevices.size(); - for (int i = 0; i < count; i++) { - final DisplayDevice d = mDisplayDevices.get(i); - if (uniqueId.equals(d.getUniqueId())) { - return d; + public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) { + for (int i = mDisplayDevices.size() - 1; i >= 0; i--) { + final DisplayDevice device = mDisplayDevices.get(i); + if (address.equals(device.getDisplayDeviceInfoLocked().address)) { + return device; } } return null; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 01fee5645475..6a229417316d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -106,9 +106,9 @@ import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.AnimationThread; @@ -1188,6 +1188,7 @@ public final class DisplayManagerService extends SystemService { final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device); final int state; final int displayId = display.getDisplayIdLocked(); + if (display.isEnabled()) { state = mDisplayStates.get(displayId); } else { @@ -1564,12 +1565,6 @@ public final class DisplayManagerService extends SystemService { } } - void setFoldOverride(Boolean isFolded) { - synchronized (mSyncRoot) { - mLogicalDisplayMapper.setFoldOverrideLocked(isFolded); - } - } - private void clearViewportsLocked() { mViewports.clear(); } diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index aaea15a27e01..d1d04966bdec 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -60,8 +60,6 @@ class DisplayManagerShellCommand extends ShellCommand { return setDisplayModeDirectorLoggingEnabled(false); case "dwb-set-cct": return setAmbientColorTemperatureOverride(); - case "set-fold": - return setFoldOverride(); default: return handleDefaultCommands(cmd); } @@ -92,8 +90,6 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" Disable display mode director logging."); pw.println(" dwb-set-cct CCT"); pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable)."); - pw.println(" set-fold [fold|unfold|reset]"); - pw.println(" Simulates the 'fold' state of a device. 'reset' for default behavior."); pw.println(); Intent.printIntentArgsHelp(pw , ""); } @@ -165,26 +161,4 @@ class DisplayManagerShellCommand extends ShellCommand { mService.setAmbientColorTemperatureOverride(cct); return 0; } - - private int setFoldOverride() { - String state = getNextArg(); - if (state == null) { - getErrPrintWriter().println("Error: no parameter specified for set-fold"); - return 1; - } - final Boolean isFolded; - if ("fold".equals(state)) { - isFolded = true; - } else if ("unfold".equals(state)) { - isFolded = false; - } else if ("reset".equals(state)) { - isFolded = null; - } else { - getErrPrintWriter().println("Error: Invalid fold state request: " + state); - return 1; - } - - mService.setFoldOverride(isFolded); - return 0; - } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 2df336528939..9320f5027ce0 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -53,8 +53,8 @@ import android.util.Slog; import android.util.TimeUtils; import android.view.Display; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.app.IBatteryStats; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 173adce00cd9..1d20d878fb81 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -26,7 +26,7 @@ import android.util.Slog; import android.view.Choreographer; import android.view.Display; -import com.android.internal.BrightnessSynchronizer; +import com.android.internal.display.BrightnessSynchronizer; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 73ebb2e48037..3b66236c9f0f 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -38,7 +38,7 @@ import android.view.DisplayEventReceiver; import android.view.RoundedCorners; import android.view.SurfaceControl; -import com.android.internal.BrightnessSynchronizer; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 80781d280d5e..20b133ce4d0a 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -32,6 +32,7 @@ import android.view.SurfaceControl; import com.android.server.wm.utils.InsetUtils; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Arrays; import java.util.Objects; @@ -718,6 +719,7 @@ final class LogicalDisplay { public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); pw.println("mLayerStack=" + mLayerStack); + pw.println("mIsEnabled=" + mIsEnabled); pw.println("mHasContent=" + mHasContent); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); pw.println("mRequestedColorMode=" + mRequestedColorMode); @@ -731,4 +733,11 @@ final class LogicalDisplay { pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides)); pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids); } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + dumpLocked(new PrintWriter(sw)); + return sw.toString(); + } } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 16c4b2641433..a054db533e3f 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -27,10 +27,10 @@ import android.view.Display; import android.view.DisplayEventReceiver; import android.view.DisplayInfo; +import com.android.server.display.layout.Layout; import java.io.PrintWriter; import java.util.Arrays; -import java.util.Objects; import java.util.function.Consumer; /** @@ -75,40 +75,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final boolean mSingleDisplayDemoMode; /** - * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay - * when {@link mIsFolded} is set to {@code true}. - */ - private String mDisplayIdToUseWhenFolded; - - /** - * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay - * when {@link mIsFolded} is set to {@code false}. - */ - private String mDisplayIdToUseWhenUnfolded; - - /** Overrides the folded state of the device. For use with ADB commands. */ - private Boolean mIsFoldedOverride; - - /** Saves the last device fold state. */ - private boolean mIsFolded; - - /** * List of all logical displays indexed by logical display id. * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache. * TODO: multi-display - Move the aforementioned comment? */ private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>(); - private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1; - private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; /** A mapping from logical display id to display group. */ private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>(); private final DisplayDeviceRepository mDisplayDeviceRepo; + private final DeviceStateToLayoutMap mDeviceStateToLayoutMap; private final Listener mListener; private final int[] mFoldedDeviceStates; + private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; + private Layout mCurrentLayout = null; + private boolean mIsFolded = false; + LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) { mDisplayDeviceRepo = repo; mListener = listener; @@ -118,7 +103,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mFoldedDeviceStates = context.getResources().getIntArray( com.android.internal.R.array.config_foldedDeviceStates); - loadFoldedDisplayConfig(context); + mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context); } @Override @@ -213,13 +198,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { ipw.increaseIndent(); ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); - ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId); + + ipw.println("mCurrentLayout=" + mCurrentLayout); final int logicalDisplayCount = mLogicalDisplays.size(); ipw.println(); ipw.println("Logical Displays: size=" + logicalDisplayCount); - - for (int i = 0; i < logicalDisplayCount; i++) { int displayId = mLogicalDisplays.keyAt(i); LogicalDisplay display = mLogicalDisplays.valueAt(i); @@ -229,6 +213,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { ipw.decreaseIndent(); ipw.println(); } + mDeviceStateToLayoutMap.dumpLocked(ipw); } void setDeviceStateLocked(int state) { @@ -244,79 +229,55 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { void setDeviceFoldedLocked(boolean isFolded) { mIsFolded = isFolded; - if (mIsFoldedOverride != null) { - isFolded = mIsFoldedOverride.booleanValue(); + + // Until we have fully functioning state mapping, use hardcoded states based on isFolded + final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED + : DeviceStateToLayoutMap.STATE_UNFOLDED; + + if (DEBUG) { + Slog.d(TAG, "New device state: " + state); } - if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null - || mLogicalDisplays.size() < 2) { - // Do nothing if this behavior is disabled or there are less than two displays. + final Layout layout = mDeviceStateToLayoutMap.get(state); + if (layout == null) { return; } - - final DisplayDevice deviceFolded = - mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded); - final DisplayDevice deviceUnfolded = - mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded); - if (deviceFolded == null || deviceUnfolded == null) { - // If the expected devices for folding functionality are not present, return early. + final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY); + if (displayLayout == null) { return; } - - // Find the associated LogicalDisplays for the configured "folding" DeviceDisplays. - final LogicalDisplay displayFolded = getLocked(deviceFolded); - final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded); - if (displayFolded == null || displayUnfolded == null) { - // If the expected displays are not present, return early. + final DisplayDevice newDefaultDevice = + mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress()); + if (newDefaultDevice == null) { return; } - // Find out which display is currently default and which is disabled. final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); - final LogicalDisplay disabledDisplay; - if (defaultDisplay == displayFolded) { - disabledDisplay = displayUnfolded; - } else if (defaultDisplay == displayUnfolded) { - disabledDisplay = displayFolded; - } else { - // If neither folded or unfolded displays are currently set to the default display, we - // are in an unknown state and it's best to log the error and bail. - Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two" - + " configured displays were set up as the default display. Default: " - + defaultDisplay.getDisplayInfoLocked() + ", ConfiguredDisplays: [ folded=" - + displayFolded.getDisplayInfoLocked() + ", unfolded=" - + displayUnfolded.getDisplayInfoLocked() + " ]"); + mCurrentLayout = layout; + + // If we're already set up accurately, return early + if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) { return; } - if (isFolded == (defaultDisplay == displayFolded)) { - // Nothing to do, already in the right state. + // We need to swap the default display's display-device with the one that is supposed + // to be the default in the new layout. + final LogicalDisplay displayToSwap = getLocked(newDefaultDevice); + if (displayToSwap == null) { + Slog.w(TAG, "Canceling display swap - unexpected empty second display for: " + + newDefaultDevice); return; } - - // Everything was checked and we need to swap, lets swap. - displayFolded.swapDisplaysLocked(displayUnfolded); + defaultDisplay.swapDisplaysLocked(displayToSwap); // We ensure that the non-default Display is always forced to be off. This was likely // already done in a previous iteration, but we do it with each swap in case something in // the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example. defaultDisplay.setEnabled(true); - disabledDisplay.setEnabled(false); + displayToSwap.setEnabled(false); // Update the world updateLogicalDisplaysLocked(); - - if (DEBUG) { - Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? " - + defaultDisplay.getDisplayInfoLocked()); - } - } - - void setFoldOverrideLocked(Boolean isFolded) { - if (!Objects.equals(isFolded, mIsFoldedOverride)) { - mIsFoldedOverride = isFolded; - setDeviceFoldedLocked(mIsFolded); - } } private void handleDisplayDeviceAddedLocked(DisplayDevice device) { @@ -333,7 +294,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return; } - final int displayId = assignDisplayIdLocked(isDefault); + final int displayId = Layout.assignDisplayIdLocked(isDefault); final int layerStack = assignLayerStackLocked(displayId); final DisplayGroup displayGroup; @@ -356,8 +317,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return; } - mLogicalDisplays.put(displayId, display); + // For foldable devices, we start the internal non-default displays as disabled. + // TODO - b/168208162 - this will be removed when we recalculate the layout with each + // display-device addition. + if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL + && !isDefault) { + display.setEnabled(false); + } + mLogicalDisplays.put(displayId, display); displayGroup.addDisplayLocked(display); mDisplayIdToGroupMap.append(displayId, displayGroup); @@ -375,6 +343,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(), LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED); } + + if (DEBUG) { + Slog.d(TAG, "New Display added: " + display); + } } /** @@ -466,10 +438,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } } - private int assignDisplayIdLocked(boolean isDefault) { - return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++; - } - private int assignDisplayGroupIdLocked(boolean isDefault) { return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++; } @@ -480,21 +448,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return displayId; } - private void loadFoldedDisplayConfig(Context context) { - final String[] displayIds = context.getResources().getStringArray( - com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds); - - if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0]) - || TextUtils.isEmpty(displayIds[1])) { - Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds) - + "]"); - return; - } - - mDisplayIdToUseWhenFolded = displayIds[0]; - mDisplayIdToUseWhenUnfolded = displayIds[1]; - } - public interface Listener { void onLogicalDisplayEventLocked(LogicalDisplay display, int event); void onDisplayGroupEventLocked(int groupId, int event); diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index 7916d816dc9b..26004a8da1a1 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -20,7 +20,7 @@ import android.animation.ValueAnimator; import android.util.FloatProperty; import android.view.Choreographer; -import com.android.internal.BrightnessSynchronizer; +import com.android.internal.display.BrightnessSynchronizer; /** * A custom animator that progressively updates a property value at diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java new file mode 100644 index 000000000000..18f39e6ade1d --- /dev/null +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -0,0 +1,154 @@ +/* + * 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.display.layout; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.NonNull; +import android.util.Slog; +import android.view.DisplayAddress; + +import java.util.ArrayList; +import java.util.List; + +/** + * Holds a collection of {@link Display}s. A single instance of this class describes + * how to organize one or more DisplayDevices into LogicalDisplays for a particular device + * state. For example, there may be one instance of this class to describe display layout when + * a foldable device is folded, and a second instance for when the device is unfolded. + */ +public class Layout { + private static final String TAG = "Layout"; + private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1; + + private final List<Display> mDisplays = new ArrayList<>(2); + + /** + * @return The default display ID, or a new unique one to use. + */ + public static int assignDisplayIdLocked(boolean isDefault) { + return isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++; + } + + @Override + public String toString() { + return mDisplays.toString(); + } + + /** + * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice. + * + * @param address Address of the device. + * @param isDefault Indicates if the device is meant to be the default display. + * @return The new layout. + */ + public Display createDisplayLocked( + @NonNull DisplayAddress address, boolean isDefault) { + if (contains(address)) { + Slog.w(TAG, "Attempting to add second definition for display-device: " + address); + return null; + } + + // See if we're dealing with the "default" display + if (isDefault && getById(DEFAULT_DISPLAY) != null) { + Slog.w(TAG, "Ignoring attempt to add a second default display: " + address); + isDefault = false; + } + + // Assign a logical display ID and create the new display. + // Note that the logical display ID is saved into the layout, so when switching between + // different layouts, a logical display can be destroyed and later recreated with the + // same logical display ID. + final int logicalDisplayId = assignDisplayIdLocked(isDefault); + final Display layout = new Display(address, logicalDisplayId); + + mDisplays.add(layout); + return layout; + } + + /** + * @param address The address to check. + * + * @return True if the specified address is used in this layout. + */ + public boolean contains(@NonNull DisplayAddress address) { + final int size = mDisplays.size(); + for (int i = 0; i < size; i++) { + if (address.equals(mDisplays.get(i).getAddress())) { + return true; + } + } + return false; + } + + /** + * @param id The display ID to check. + * + * @return The display corresponding to the specified display ID. + */ + public Display getById(int id) { + for (int i = 0; i < mDisplays.size(); i++) { + Display layout = mDisplays.get(i); + if (id == layout.getLogicalDisplayId()) { + return layout; + } + } + return null; + } + + /** + * @param index The index of the display to return. + * + * @return the display at the specified index. + */ + public Display getAt(int index) { + return mDisplays.get(index); + } + + /** + * @return The number of displays defined for this layout. + */ + public int size() { + return mDisplays.size(); + } + + /** + * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. + */ + public static class Display { + private final DisplayAddress mAddress; + private final int mLogicalDisplayId; + + Display(@NonNull DisplayAddress address, int logicalDisplayId) { + mAddress = address; + mLogicalDisplayId = logicalDisplayId; + } + + @Override + public String toString() { + return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}"; + } + + public DisplayAddress getAddress() { + return mAddress; + } + + public int getLogicalDisplayId() { + return mLogicalDisplayId; + } + } +} 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 1a4c8b7d6571..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 */); @@ -5246,12 +5239,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub imeTracing.startTrace(null); }); } - } - doDump(fd, pw, args, asProto); - } - private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) { - if (useProto) { final ProtoOutputStream proto = new ProtoOutputStream(fd); dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE); proto.flush(); diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java index 43c965dde27b..42b0add6136e 100644 --- a/services/core/java/com/android/server/lights/LightsService.java +++ b/services/core/java/com/android/server/lights/LightsService.java @@ -36,9 +36,9 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.SystemService; diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 28dc5167487d..47cb43e2d4af 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -1253,7 +1253,8 @@ public class LocationManagerService extends ILocationManager.Stub { ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats = mEventLog.copyAggregateStats(); for (int i = 0; i < aggregateStats.size(); i++) { - ipw.println(aggregateStats.keyAt(i)); + ipw.print(aggregateStats.keyAt(i)); + ipw.println(":"); ipw.increaseIndent(); ArrayMap<String, LocationEventLog.AggregateStats> providerStats = aggregateStats.valueAt(i); diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index 67060fc2c082..865d41f1baee 100644 --- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -164,6 +164,7 @@ public class LocationEventLog extends LocalEventLog { if (Build.IS_DEBUGGABLE || D) { addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity); } + getAggregateStats(provider, identity.getPackageName()).markLocationDelivered(); } /** Logs that the location power save mode has changed. */ @@ -397,6 +398,8 @@ public class LocationEventLog extends LocalEventLog { private int mActiveRequestCount; @GuardedBy("this") private int mForegroundRequestCount; + @GuardedBy("this") + private int mDeliveredLocationCount; @GuardedBy("this") private long mFastestIntervalMs = Long.MAX_VALUE; @@ -464,6 +467,10 @@ public class LocationEventLog extends LocalEventLog { Preconditions.checkState(mForegroundRequestCount >= 0); } + synchronized void markLocationDelivered() { + mDeliveredLocationCount++; + } + public synchronized void updateTotals() { if (mAddedRequestCount > 0) { long realtimeMs = SystemClock.elapsedRealtime(); @@ -488,7 +495,8 @@ public class LocationEventLog extends LocalEventLog { + intervalToString(mSlowestIntervalMs) + ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs) + "/" + formatDuration(mActiveTimeTotalMs) + "/" - + formatDuration(mForegroundTimeTotalMs); + + formatDuration(mForegroundTimeTotalMs) + ", locations = " + + mDeliveredLocationCount; } private static String intervalToString(long intervalMs) { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 28c90e965e47..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); @@ -2372,10 +2377,17 @@ public class LockSettingsService extends ILockSettings.Stub { public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { enforceShell(); + final int origPid = Binder.getCallingPid(); + final int origUid = Binder.getCallingUid(); + + // The original identity is an opaque integer. final long origId = Binder.clearCallingIdentity(); + Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid); try { - (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec( - this, in, out, err, args, callback, resultReceiver); + final LockSettingsShellCommand command = + new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid, + origUid); + command.exec(this, in, out, err, args, callback, resultReceiver); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 834cf0536865..67fae05c64f4 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -21,8 +21,11 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTE import android.app.ActivityManager; import android.app.admin.PasswordMetrics; +import android.content.Context; import android.os.ShellCommand; +import android.os.SystemProperties; import android.text.TextUtils; +import android.util.Slog; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.RequestThrottledException; @@ -43,15 +46,25 @@ class LockSettingsShellCommand extends ShellCommand { private static final String COMMAND_VERIFY = "verify"; private static final String COMMAND_GET_DISABLED = "get-disabled"; private static final String COMMAND_REMOVE_CACHE = "remove-cache"; + private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE = + "set-resume-on-reboot-provider-package"; private static final String COMMAND_HELP = "help"; private int mCurrentUserId; private final LockPatternUtils mLockPatternUtils; + private final Context mContext; + private final int mCallingPid; + private final int mCallingUid; + private String mOld = ""; private String mNew = ""; - LockSettingsShellCommand(LockPatternUtils lockPatternUtils) { + LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid, + int callingUid) { mLockPatternUtils = lockPatternUtils; + mCallingPid = callingPid; + mCallingUid = callingUid; + mContext = context; } @Override @@ -68,6 +81,7 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_HELP: case COMMAND_GET_DISABLED: case COMMAND_SET_DISABLED: + case COMMAND_SET_ROR_PROVIDER_PACKAGE: break; default: getErrPrintWriter().println( @@ -80,6 +94,9 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_REMOVE_CACHE: runRemoveCache(); return 0; + case COMMAND_SET_ROR_PROVIDER_PACKAGE: + runSetResumeOnRebootProviderPackage(); + return 0; case COMMAND_HELP: onHelp(); return 0; @@ -170,6 +187,9 @@ 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(" set-resume-on-reboot-provider-package <package_name>"); + pw.println(" Sets the package name for server based resume on reboot service " + + "provider."); pw.println(""); } } @@ -256,6 +276,17 @@ class LockSettingsShellCommand extends ShellCommand { return true; } + private boolean runSetResumeOnRebootProviderPackage() { + final String packageName = mNew; + String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE; + Slog.i(TAG, "Setting " + name + " to " + packageName); + + mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE, + mCallingPid, mCallingUid, TAG); + SystemProperties.set(name, packageName); + return true; + } + private boolean runClear() { LockscreenCredential none = LockscreenCredential.createNone(); if (!isNewCredentialSufficient(none)) { diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 06962d414009..53b62ca6ecb5 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; @@ -148,6 +157,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 +216,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 +240,49 @@ 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); + RebootEscrowKey escrowKey; + try { + escrowKey = getAndClearRebootEscrowKey(kk); + } catch (IOException e) { + scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users, + rebootEscrowUsers); + return; + } + 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); + onGetRebootEscrowKeyFailed(users); return; } @@ -249,7 +309,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..9d09637cdc74 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,20 @@ 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; } + 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 +150,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/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java index a1e18bd5a6bd..9c471b85eb76 100644 --- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java +++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java @@ -31,6 +31,7 @@ import android.os.IBinder; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.resumeonreboot.IResumeOnRebootService; @@ -55,6 +56,10 @@ public class ResumeOnRebootServiceProvider { Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; private static final String TAG = "ResumeOnRebootServiceProvider"; + // The system property name that overrides the default service provider package name. + static final String PROP_ROR_PROVIDER_PACKAGE = + "persist.sys.resume_on_reboot_provider_package"; + private final Context mContext; private final PackageManager mPackageManager; @@ -72,12 +77,19 @@ public class ResumeOnRebootServiceProvider { private ServiceInfo resolveService() { Intent intent = new Intent(); intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); - if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { - intent.setPackage(PROVIDER_PACKAGE); + int queryFlag = PackageManager.GET_SERVICES; + String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, ""); + if (!testAppName.isEmpty()) { + Slog.i(TAG, "Using test app: " + testAppName); + intent.setPackage(testAppName); + } else { + queryFlag |= PackageManager.MATCH_SYSTEM_ONLY; + if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { + intent.setPackage(PROVIDER_PACKAGE); + } } - List<ResolveInfo> resolvedIntents = - mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY); + List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag); for (ResolveInfo resolvedInfo : resolvedIntents) { if (resolvedInfo.serviceInfo != null && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { @@ -120,6 +132,7 @@ public class ResumeOnRebootServiceProvider { if (mServiceConnection != null) { mContext.unbindService(mServiceConnection); } + mBinder = null; } /** Bind to the service */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4c3dfbff3f87..1591e999e893 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -110,6 +110,8 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; +import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER; +import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; import static com.android.server.utils.PriorityDump.PRIORITY_ARG; import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL; import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL; @@ -175,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; @@ -284,7 +287,6 @@ import com.android.server.notification.toast.CustomToastRecord; import com.android.server.notification.toast.TextToastRecord; import com.android.server.notification.toast.ToastRecord; import com.android.server.pm.PackageManagerService; -import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.utils.quota.MultiRateLimiter; @@ -358,7 +360,7 @@ public class NotificationManagerService extends SystemService { private static final int MESSAGE_RECONSIDER_RANKING = 1000; private static final int MESSAGE_RANKING_SORT = 1001; - static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT; + static final int LONG_DELAY = TOAST_WINDOW_TIMEOUT - TOAST_WINDOW_ANIM_BUFFER; // 3.5 seconds static final int SHORT_DELAY = 2000; // 2 seconds // 1 second past the ANR timeout. @@ -3056,7 +3058,7 @@ public class NotificationManagerService extends SystemService { // If the callback fails, this will remove it from the list, so don't // assume that it's valid after this. if (index == 0) { - showNextToastLocked(); + showNextToastLocked(false); } } finally { Binder.restoreCallingIdentity(callingId); @@ -7392,7 +7394,7 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mToastQueue") - void showNextToastLocked() { + void showNextToastLocked(boolean lastToastWasTextRecord) { if (mIsCurrentToastShown) { return; // Don't show the same toast twice. } @@ -7406,7 +7408,7 @@ public class NotificationManagerService extends SystemService { mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG); if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) { - scheduleDurationReachedLocked(record); + scheduleDurationReachedLocked(record, lastToastWasTextRecord); mIsCurrentToastShown = true; if (rateLimitingEnabled) { mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG); @@ -7477,7 +7479,7 @@ public class NotificationManagerService extends SystemService { // Show the next one. If the callback fails, this will remove // it from the list, so don't assume that the list hasn't changed // after this point. - showNextToastLocked(); + showNextToastLocked(lastToast instanceof TextToastRecord); } } @@ -7491,7 +7493,7 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mToastQueue") - private void scheduleDurationReachedLocked(ToastRecord r) + private void scheduleDurationReachedLocked(ToastRecord r, boolean lastToastWasTextRecord) { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r); @@ -7501,6 +7503,14 @@ public class NotificationManagerService extends SystemService { // preference. delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay, AccessibilityManager.FLAG_CONTENT_TEXT); + + if (lastToastWasTextRecord) { + delay += 250; // delay to account for previous toast's "out" animation + } + if (r instanceof TextToastRecord) { + delay += 333; // delay to account for this toast's "in" animation + } + mHandler.sendMessageDelayed(m, delay); } @@ -8960,7 +8970,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; @@ -9571,7 +9582,8 @@ public class NotificationManagerService extends SystemService { 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 = ","; @@ -9729,6 +9741,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 @@ -9758,15 +9782,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); } } } @@ -9791,10 +9817,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); } 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/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 402f6467e82d..af0aa769f8cc 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -217,7 +217,7 @@ public class BackgroundDexOptService extends JobService { // trade-off worth doing to save boot time work. int result = pm.performDexOptWithStatus(new DexoptOptions( pkg, - PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_POST_BOOT, DexoptOptions.DEXOPT_BOOT_COMPLETE)); if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 52fdc7983636..308e815d7659 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -27,6 +27,8 @@ import android.content.pm.IDataLoaderManager; import android.content.pm.IDataLoaderStatusListener; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; @@ -45,12 +47,20 @@ import java.util.List; public class DataLoaderManagerService extends SystemService { private static final String TAG = "DataLoaderManager"; private final Context mContext; + private final HandlerThread mThread; + private final Handler mHandler; private final DataLoaderManagerBinderService mBinderService; private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>(); public DataLoaderManagerService(Context context) { super(context); mContext = context; + + mThread = new HandlerThread(TAG); + mThread.start(); + + mHandler = new Handler(mThread.getLooper()); + mBinderService = new DataLoaderManagerBinderService(); } @@ -62,7 +72,7 @@ public class DataLoaderManagerService extends SystemService { final class DataLoaderManagerBinderService extends IDataLoaderManager.Stub { @Override public boolean bindToDataLoader(int dataLoaderId, DataLoaderParamsParcel params, - IDataLoaderStatusListener listener) { + long bindDelayMs, IDataLoaderStatusListener listener) { synchronized (mServiceConnections) { if (mServiceConnections.get(dataLoaderId) != null) { return true; @@ -76,19 +86,21 @@ public class DataLoaderManagerService extends SystemService { } // Binds to the specific data loader service. - DataLoaderServiceConnection connection = new DataLoaderServiceConnection(dataLoaderId, - listener); + final DataLoaderServiceConnection connection = new DataLoaderServiceConnection( + dataLoaderId, listener); - Intent intent = new Intent(); + final Intent intent = new Intent(); intent.setComponent(dataLoaderComponent); - if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, - UserHandle.of(UserHandle.getCallingUserId()))) { - Slog.e(TAG, - "Failed to bind to: " + dataLoaderComponent + " for ID=" + dataLoaderId); - mContext.unbindService(connection); - return false; - } - return true; + + return mHandler.postDelayed(() -> { + if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, + mHandler, UserHandle.of(UserHandle.getCallingUserId()))) { + Slog.e(TAG, + "Failed to bind to: " + dataLoaderComponent + " for ID=" + + dataLoaderId); + mContext.unbindService(connection); + } + }, bindDelayMs); } /** diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index f9ae9b8f6879..e91bb46657e1 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -929,6 +929,7 @@ public class LauncherAppsService extends SystemService { // Flag for bubble to make behaviour match documentLaunchMode=always. intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + intents[0].putExtra(Intent.EXTRA_IS_BUBBLED, true); } intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index fc02b3439d16..b9e3e0f4450b 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -687,7 +687,8 @@ public class PackageDexOptimizer { boolean generateCompactDex = true; switch (compilationReason) { case PackageManagerService.REASON_FIRST_BOOT: - case PackageManagerService.REASON_BOOT: + case PackageManagerService.REASON_BOOT_AFTER_OTA: + case PackageManagerService.REASON_POST_BOOT: case PackageManagerService.REASON_INSTALL: generateCompactDex = false; } 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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 7c425698e507..0ce26739b51c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -3778,7 +3778,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) { + final long bindDelayMs = 0; + if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), bindDelayMs, + statusListener)) { throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, "Failed to initialize data loader"); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4db65501de06..7b9cf734fd30 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -780,17 +780,18 @@ public class PackageManagerService extends IPackageManager.Stub // Compilation reasons. public static final int REASON_UNKNOWN = -1; public static final int REASON_FIRST_BOOT = 0; - public static final int REASON_BOOT = 1; - public static final int REASON_INSTALL = 2; - public static final int REASON_INSTALL_FAST = 3; - public static final int REASON_INSTALL_BULK = 4; - public static final int REASON_INSTALL_BULK_SECONDARY = 5; - public static final int REASON_INSTALL_BULK_DOWNGRADED = 6; - public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7; - public static final int REASON_BACKGROUND_DEXOPT = 8; - public static final int REASON_AB_OTA = 9; - public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10; - public static final int REASON_SHARED = 11; + public static final int REASON_BOOT_AFTER_OTA = 1; + public static final int REASON_POST_BOOT = 2; + public static final int REASON_INSTALL = 3; + public static final int REASON_INSTALL_FAST = 4; + public static final int REASON_INSTALL_BULK = 5; + public static final int REASON_INSTALL_BULK_SECONDARY = 6; + public static final int REASON_INSTALL_BULK_DOWNGRADED = 7; + public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8; + public static final int REASON_BACKGROUND_DEXOPT = 9; + public static final int REASON_AB_OTA = 10; + public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11; + public static final int REASON_SHARED = 12; public static final int REASON_LAST = REASON_SHARED; @@ -11637,10 +11638,7 @@ public class PackageManagerService extends IPackageManager.Stub // first boot, as they do not have profile data. boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade; - // We need to re-extract after a pruned cache, as AoT-ed files will be out of date. - boolean causePrunedCache = VMRuntime.didPruneDalvikCache(); - - if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) { + if (!causeUpgrade && !causeFirstBoot) { return; } @@ -11657,7 +11655,7 @@ public class PackageManagerService extends IPackageManager.Stub final long startTime = System.nanoTime(); final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */, - causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT, + causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA, false /* bootComplete */); final int elapsedTimeSeconds = @@ -26213,30 +26211,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void setKeepUninstalledPackages(final List<String> packageList) { - Preconditions.checkNotNull(packageList); - List<String> removedFromList = null; - synchronized (mLock) { - if (mKeepUninstalledPackages != null) { - final int packagesCount = mKeepUninstalledPackages.size(); - for (int i = 0; i < packagesCount; i++) { - String oldPackage = mKeepUninstalledPackages.get(i); - if (packageList != null && packageList.contains(oldPackage)) { - continue; - } - if (removedFromList == null) { - removedFromList = new ArrayList<>(); - } - removedFromList.add(oldPackage); - } - } - mKeepUninstalledPackages = new ArrayList<>(packageList); - if (removedFromList != null) { - final int removedCount = removedFromList.size(); - for (int i = 0; i < removedCount; i++) { - deletePackageIfUnusedLPr(removedFromList.get(i)); - } - } - } + PackageManagerService.this.setKeepUninstalledPackagesInternal(packageList); } @Override @@ -27734,6 +27709,43 @@ public class PackageManagerService extends IPackageManager.Stub public DomainVerificationService.Connection getDomainVerificationConnection() { return mDomainVerificationConnection; } + + @Override + public void setKeepUninstalledPackages(List<String> packageList) { + mContext.enforceCallingPermission( + Manifest.permission.KEEP_UNINSTALLED_PACKAGES, + "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission"); + Objects.requireNonNull(packageList); + + setKeepUninstalledPackagesInternal(packageList); + } + + private void setKeepUninstalledPackagesInternal(List<String> packageList) { + Preconditions.checkNotNull(packageList); + List<String> removedFromList = null; + synchronized (mLock) { + if (mKeepUninstalledPackages != null) { + final int packagesCount = mKeepUninstalledPackages.size(); + for (int i = 0; i < packagesCount; i++) { + String oldPackage = mKeepUninstalledPackages.get(i); + if (packageList != null && packageList.contains(oldPackage)) { + continue; + } + if (removedFromList == null) { + removedFromList = new ArrayList<>(); + } + removedFromList.add(oldPackage); + } + } + mKeepUninstalledPackages = new ArrayList<>(packageList); + if (removedFromList != null) { + final int removedCount = removedFromList.size(); + for (int i = 0; i < removedCount; i++) { + deletePackageIfUnusedLPr(removedFromList.get(i)); + } + } + } + } } interface PackageSender { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index 9cd55a6bb07e..636db111be88 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -29,7 +29,8 @@ public class PackageManagerServiceCompilerMapping { // Names for compilation reasons. public static final String REASON_STRINGS[] = { "first-boot", - "boot", + "boot-after-ota", + "post-boot", "install", "install-fast", "install-bulk", 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/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 139654ef4561..3576950bd3a6 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -587,7 +587,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { private static final int TRON_COMPILATION_REASON_ERROR = 0; private static final int TRON_COMPILATION_REASON_UNKNOWN = 1; private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2; - private static final int TRON_COMPILATION_REASON_BOOT = 3; + private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3; private static final int TRON_COMPILATION_REASON_INSTALL = 4; private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5; private static final int TRON_COMPILATION_REASON_AB_OTA = 6; @@ -605,6 +605,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18; private static final int TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19; + private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20; + private static final int TRON_COMPILATION_REASON_POST_BOOT = 21; // The annotation to add as a suffix to the compilation reason when dexopt was // performed with dex metadata. @@ -618,7 +620,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN; case "error" : return TRON_COMPILATION_REASON_ERROR; case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT; - case "boot" : return TRON_COMPILATION_REASON_BOOT; + case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA; + case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT; case "install" : return TRON_COMPILATION_REASON_INSTALL; case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT; case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA; 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/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index 30c334d22b6a..32bee5809b11 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -38,6 +38,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collection; import java.util.Objects; +import java.util.Set; /** * Permission definition. @@ -345,6 +346,14 @@ public final class Permission { return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0; } + public boolean isKnownSigner() { + return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0; + } + + public Set<String> getKnownCerts() { + return mPermissionInfo.knownCerts; + } + public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) { if (!oldPackageName.equals(mPermissionInfo.packageName)) { return; 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 aff871118a34..e486f087535b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2590,6 +2590,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { boolean runtimePermissionsRevoked = false; int[] updatedUserIds = EMPTY_INT_ARRAY; + ArraySet<String> isPrivilegedPermissionAllowlisted = null; ArraySet<String> shouldGrantSignaturePermission = null; ArraySet<String> shouldGrantInternalPermission = null; final List<String> requestedPermissions = pkg.getRequestedPermissions(); @@ -2604,7 +2605,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (permission == null) { continue; } - if (permission.isSignature() && (shouldGrantSignaturePermission(pkg, permission) + if (permission.isPrivileged() + && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) { + if (isPrivilegedPermissionAllowlisted == null) { + isPrivilegedPermissionAllowlisted = new ArraySet<>(); + } + isPrivilegedPermissionAllowlisted.add(permissionName); + } + if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission) || shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) { if (shouldGrantSignaturePermission == null) { shouldGrantSignaturePermission = new ArraySet<>(); @@ -2830,13 +2838,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { if ((bp.isNormal() && shouldGrantNormalPermission) || (bp.isSignature() - && ((shouldGrantSignaturePermission != null - && shouldGrantSignaturePermission.contains(permName)) + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantSignaturePermission, + permName) || ((bp.isDevelopment() || bp.isRole()) && origState.isPermissionGranted(permName)))) || (bp.isInternal() - && ((shouldGrantInternalPermission != null - && shouldGrantInternalPermission.contains(permName)) + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantInternalPermission, + permName) || ((bp.isDevelopment() || bp.isRole()) && origState.isPermissionGranted(permName))))) { // Grant an install permission. @@ -3343,7 +3355,92 @@ public class PermissionManagerService extends IPermissionManager.Stub { return allowed; } - private boolean shouldGrantSignaturePermission(@NonNull AndroidPackage pkg, + private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg, + @NonNull PackageSetting packageSetting, @NonNull Permission permission) { + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) { + return true; + } + final String packageName = pkg.getPackageName(); + if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) { + return true; + } + if (!pkg.isPrivileged()) { + return true; + } + if (!Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)) { + return true; + } + final String permissionName = permission.getName(); + if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) { + return true; + } + // Only enforce the allowlist on boot + if (!mSystemReady + // Updated system apps do not need to be allowlisted + && !packageSetting.getPkgState().isUpdatedSystemApp()) { + final ApexManager apexManager = ApexManager.getInstance(); + final String containingApexPackageName = + apexManager.getActiveApexPackageNameContainingPackage(packageName); + final boolean isInUpdatedApex = containingApexPackageName != null + && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName, + 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"); + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { + synchronized (mLock) { + if (mPrivappPermissionsViolations == null) { + mPrivappPermissionsViolations = new ArraySet<>(); + } + mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): " + + permissionName); + } + } + } + } + return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE; + } + + private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg, + @NonNull String permission) { + final SystemConfig systemConfig = SystemConfig.getInstance(); + final Set<String> permissions; + if (pkg.isVendor()) { + permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName()); + } else if (pkg.isProduct()) { + permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName()); + } else if (pkg.isSystemExt()) { + permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName()); + } else { + permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName()); + } + return CollectionUtils.contains(permissions, permission); + } + + private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg, + @NonNull String permission) { + final SystemConfig systemConfig = SystemConfig.getInstance(); + final Set<String> permissions; + if (pkg.isVendor()) { + permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName()); + } else if (pkg.isProduct()) { + permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName()); + } else if (pkg.isSystemExt()) { + permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName()); + } else { + permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName()); + } + return CollectionUtils.contains(permissions, permission); + } + + private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg, @NonNull Permission bp) { // expect single system package String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames( @@ -3373,8 +3470,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg, @NonNull PackageSetting pkgSetting, @NonNull Permission bp) { boolean allowed = false; - final boolean isVendorPrivilegedPermission = bp.isVendorPrivileged(); - final boolean isPrivilegedPermission = bp.isPrivileged() || isVendorPrivilegedPermission; + final boolean isPrivilegedPermission = bp.isPrivileged(); final boolean isOemPermission = bp.isOem(); if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) { final String permissionName = bp.getName(); @@ -3386,19 +3482,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg; if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains( permissionName)) { - allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(disabledPkg, - true, bp)) || (isOemPermission && canGrantOemPermission(disabledPkg, + allowed = (isPrivilegedPermission && disabledPkg.isPrivileged()) + || (isOemPermission && canGrantOemPermission(disabledPkg, permissionName)); } } else { - allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(pkg, false, bp)) + allowed = (isPrivilegedPermission && pkg.isPrivileged()) || (isOemPermission && canGrantOemPermission(pkg, permissionName)); } // In any case, don't grant a privileged permission to privileged vendor apps, if // the permission's protectionLevel does not have the extra 'vendorPrivileged' // flag. - if (allowed && isPrivilegedPermission && !isVendorPrivilegedPermission - && pkg.isVendor()) { + if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) { Slog.w(TAG, "Permission " + permissionName + " cannot be granted to privileged vendor apk " + pkg.getPackageName() + " because it isn't a 'vendorPrivileged' permission."); @@ -3438,6 +3533,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { // Any pre-installed system app is allowed to get this permission. allowed = true; } + if (!allowed && bp.isKnownSigner()) { + // If the permission is to be granted to a known signer then check if any of this + // app's signing certificates are in the trusted certificate digest Set. + allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts()); + } // Deferred to be checked under permission data lock inside restorePermissionState(). //if (!allowed && bp.isDevelopment()) { // // For development permissions, a development permission @@ -3536,90 +3636,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { return mPackageManagerInt.getPackageSetting(sourcePackageName); } - private boolean canGrantPrivilegedPermission(@NonNull AndroidPackage pkg, - boolean isUpdatedSystemApp, @NonNull Permission permission) { - if (!pkg.isPrivileged()) { - return false; - } - final boolean isPlatformPermission = PLATFORM_PACKAGE_NAME.equals( - permission.getPackageName()); - if (!isPlatformPermission) { - return true; - } - if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) { - return true; - } - final String permissionName = permission.getName(); - if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) { - return true; - } - // Only enforce the allowlist on boot - if (!mSystemReady - // Updated system apps do not need to be allowlisted - && !isUpdatedSystemApp) { - final ApexManager apexManager = ApexManager.getInstance(); - final String packageName = pkg.getPackageName(); - final String containingApexPackageName = - apexManager.getActiveApexPackageNameContainingPackage(packageName); - final boolean isInUpdatedApex = containingApexPackageName != null - && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName, - 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"); - if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - synchronized (mLock) { - if (mPrivappPermissionsViolations == null) { - mPrivappPermissionsViolations = new ArraySet<>(); - } - mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): " - + permissionName); - } - } - } - } - return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE; - } - - private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg, - @NonNull String permission) { - final SystemConfig systemConfig = SystemConfig.getInstance(); - final Set<String> permissions; - if (pkg.isVendor()) { - permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName()); - } else if (pkg.isProduct()) { - permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName()); - } else if (pkg.isSystemExt()) { - permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName()); - } else { - permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName()); - } - return permissions != null && permissions.contains(permission); - } - - private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg, - @NonNull String permission) { - final SystemConfig systemConfig = SystemConfig.getInstance(); - final Set<String> permissions; - if (pkg.isVendor()) { - permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName()); - } else if (pkg.isProduct()) { - permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName()); - } else if (pkg.isSystemExt()) { - permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName()); - } else { - permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName()); - } - return permissions != null && permissions.contains(permission); - } - private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) { if (!pkg.isOem()) { return false; 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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index bc819617332d..6a441f1830d1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -346,8 +346,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */ static final int WAITING_FOR_DRAWN_TIMEOUT = 1000; - /** Amount of time (in milliseconds) a toast window can be shown. */ - public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds + /** + * Extra time for additional SystemUI animations. + * <p>Since legacy apps can add Toast windows directly instead of using Toast APIs, + * {@link DisplayPolicy} ensures that the window manager removes toast windows after + * TOAST_WINDOW_TIMEOUT. We increase this timeout by TOAST_WINDOW_ANIM_BUFFER to account for + * SystemUI's in/out toast animations, so that the toast text is still shown for a minimum + * of 3.5 seconds and the animations are finished before window manager removes the window. + */ + public static final int TOAST_WINDOW_ANIM_BUFFER = 600; + + /** + * Amount of time (in milliseconds) a toast window can be shown before it's automatically + * removed by window manager. + */ + public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER; /** * Lock protecting internal state. Must not call out into window diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 8e0d632dd1a8..88fdc4aad5cf 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -90,11 +90,11 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.KeyEvent; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; +import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; 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..0d4360058788 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -1612,9 +1612,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 +1619,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]; } }); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index bbbd19f7a49e..b210339adf79 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -18,7 +18,7 @@ package com.android.server.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.timedetector.ExternalTimeSuggestion; +import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ITimeDetectorService; import android.app.timedetector.ManualTimeSuggestion; diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 6a4c276de2dd..792f372b0c49 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -18,7 +18,7 @@ package com.android.server.timedetector; import android.annotation.IntDef; import android.annotation.NonNull; -import android.app.timedetector.ExternalTimeSuggestion; +import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 7cd4184f2bc9..c4c620c41918 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -23,7 +23,7 @@ import static java.util.stream.Collectors.joining; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlarmManager; -import android.app.timedetector.ExternalTimeSuggestion; +import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index bee66637fb2f..04dac7c2b198 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -92,13 +92,6 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi @GuardedBy("mLock") private boolean mForceStop; - // TODO(b/159207608): Remove this constructor once VibratorService is removed - public VibrationThread(Vibration vib, VibratorController vibrator, - PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, - VibrationCallbacks callbacks) { - this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks); - } - public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, VibrationCallbacks callbacks) { @@ -286,12 +279,6 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi return filteredEffects; } - private static SparseArray<VibratorController> toSparseArray(VibratorController controller) { - SparseArray<VibratorController> array = new SparseArray<>(1); - array.put(controller.getVibratorInfo().getId(), controller); - return array; - } - /** * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code * startIndex} until the next time it's vibrating amplitude is zero. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 3456e51d028a..79f8229c6162 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); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index bd93e045cdc7..ceebe9550846 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); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 1c4b03498144..a7abf6a4fa99 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -149,7 +149,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/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index 4e47984fa75c..a6029cd28029 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -73,10 +73,6 @@ 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) { - // TODO(b/167946816): remove this once VibratorService is removed. - if (vibratorId < 0) { - return std::move(std::make_unique<vibrator::HalController>()); - } vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager(); if (manager == nullptr) { return nullptr; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index bd109922cb42..b063e6727f5b 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; @@ -1101,7 +1101,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 +1114,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( @@ -7522,19 +7527,23 @@ 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)); + 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) { @@ -12224,6 +12233,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 @@ -16328,8 +16363,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime()); setLocale(provisioningParams.getLocale()); + final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode() + ? UserHandle.USER_SYSTEM : caller.getUserId(); if (!removeNonRequiredAppsForManagedDevice( - caller.getUserId(), + deviceOwnerUserId, provisioningParams.isLeaveAllSystemAppsEnabled(), deviceAdmin)) { throw new ServiceSpecificException( @@ -16337,15 +16374,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "PackageManager failed to remove non required apps."); } + if (!setActiveAdminAndDeviceOwner( - caller.getUserId(), deviceAdmin, provisioningParams.getOwnerName())) { + deviceOwnerUserId, deviceAdmin, provisioningParams.getOwnerName())) { throw new ServiceSpecificException( PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED, "Failed to set device owner."); } disallowAddUser(); - setAdminCanGrantSensorsPermissionForUserUnchecked(caller.getUserId(), + setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId, provisioningParams.canDeviceOwnerGrantSensorsPermissions()); } catch (Exception e) { DevicePolicyEventLogger @@ -16388,30 +16426,42 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private boolean removeNonRequiredAppsForManagedDevice( - int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) { + @UserIdInt int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) { Set<String> packagesToDelete = leaveAllSystemAppsEnabled ? Collections.emptySet() : mOverlayPackagesProvider.getNonRequiredApps( admin, userId, ACTION_PROVISION_MANAGED_DEVICE); + + removeNonInstalledPackages(packagesToDelete, userId); if (packagesToDelete.isEmpty()) { + Slog.i(LOG_TAG, "No packages to delete on user " + userId); return true; } + NonRequiredPackageDeleteObserver packageDeleteObserver = new NonRequiredPackageDeleteObserver(packagesToDelete.size()); for (String packageName : packagesToDelete) { - if (isPackageInstalledForUser(packageName, userId)) { - Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId); - mContext.getPackageManager().deletePackageAsUser( - packageName, - packageDeleteObserver, - PackageManager.DELETE_SYSTEM_APP, - userId); - } + Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId); + mContext.getPackageManager().deletePackageAsUser( + packageName, + packageDeleteObserver, + PackageManager.DELETE_SYSTEM_APP, + userId); } Slog.i(LOG_TAG, "Waiting for non required apps to be deleted"); return packageDeleteObserver.awaitPackagesDeletion(); } + private void removeNonInstalledPackages(Set<String> packages, @UserIdInt int userId) { + final Set<String> toBeRemoved = new HashSet<>(); + for (String packageName : packages) { + if (!isPackageInstalledForUser(packageName, userId)) { + toBeRemoved.add(packageName); + } + } + packages.removeAll(toBeRemoved); + } + private void disallowAddUser() { if (mInjector.userManagerIsHeadlessSystemUserMode()) { Slog.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode."); 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/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/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 56cb3d11d552..886c1e5b98ea 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -69,6 +69,14 @@ struct Constants { static constexpr auto progressUpdateInterval = 1000ms; static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2; static constexpr auto minPerUidTimeout = progressUpdateInterval * 3; + + // If DL was up and not crashing for 10mins, we consider it healthy and reset all delays. + static constexpr auto healthyDataLoaderUptime = 10min; + // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs) + static constexpr auto minBindDelay = 10s; + static constexpr auto maxBindDelay = 10000s; + static constexpr auto bindDelayMultiplier = 10; + static constexpr auto bindDelayJitterDivider = 10; }; static const Constants& constants() { @@ -386,6 +394,28 @@ void IncrementalService::onDump(int fd) { dprintf(fd, "}\n"); } +bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) { + if (ifs.dataLoaderStub->params().packageName == Constants::systemPackage) { + return true; + } + + // Check all permanent binds. + for (auto&& [_, bindPoint] : ifs.bindPoints) { + if (bindPoint.kind != BindKind::Permanent) { + continue; + } + const auto progress = getLoadingProgressFromPath(ifs, bindPoint.sourceDir, + /*stopOnFirstIncomplete=*/true); + if (!progress.isError() && !progress.fullyLoaded()) { + LOG(INFO) << "Non system mount: [" << bindPoint.sourceDir + << "], partial progress: " << progress.getProgress() * 100 << "%"; + return true; + } + } + + return false; +} + void IncrementalService::onSystemReady() { if (mSystemReady.exchange(true)) { return; @@ -396,8 +426,11 @@ void IncrementalService::onSystemReady() { std::lock_guard l(mLock); mounts.reserve(mMounts.size()); for (auto&& [id, ifs] : mMounts) { - if (ifs->mountId == id && - ifs->dataLoaderStub->params().packageName == Constants::systemPackage) { + if (ifs->mountId != id) { + continue; + } + + if (needStartDataLoaderLocked(*ifs)) { mounts.push_back(ifs); } } @@ -1539,6 +1572,11 @@ static long elapsedMcs(Duration start, Duration end) { return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); } +template <class Duration> +static constexpr auto castToMs(Duration d) { + return std::chrono::duration_cast<std::chrono::milliseconds>(d); +} + // Extract lib files from zip, create new files in incfs and write data to them // Lib files should be placed next to the APK file in the following matter: // Example: @@ -2134,9 +2172,43 @@ void IncrementalService::DataLoaderStub::setTargetStatusLocked(int status) { << status << " (current " << mCurrentStatus << ")"; } +Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() { + std::unique_lock lock(mMutex); + const auto previousBindTs = mPreviousBindTs; + const auto now = Clock::now(); + mPreviousBindTs = now; + + const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms); + if (previousBindTs.time_since_epoch() == Clock::duration::zero() || + nonCrashingInterval > Constants::healthyDataLoaderUptime) { + mPreviousBindDelay = 0ms; + return mPreviousBindDelay; + } + + constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay); + constexpr auto maxBindDelayMs = castToMs(Constants::maxBindDelay); + + const auto bindDelayMs = + std::min(std::max(mPreviousBindDelay * Constants::bindDelayMultiplier, minBindDelayMs), + maxBindDelayMs) + .count(); + const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider; + const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs; + mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs); + + return mPreviousBindDelay; +} + bool IncrementalService::DataLoaderStub::bind() { + const auto bindDelay = updateBindDelay(); + if (bindDelay > 1s) { + LOG(INFO) << "Delaying bind to " << mParams.packageName << " by " + << bindDelay.count() / 1000 << "s"; + } + bool result = false; - auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result); + auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(), + this, &result); if (!status.isOk() || !result) { LOG(ERROR) << "Failed to bind a data loader for mount " << id(); return false; @@ -2249,7 +2321,8 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount listener = mStatusListener; - if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) { + if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE || + mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) { // For unavailable, unbind from DataLoader to ensure proper re-commit. setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED); } @@ -2544,6 +2617,9 @@ void IncrementalService::DataLoaderStub::onDump(int fd) { dprintf(fd, " blockIndex: %d\n", pendingRead.block); dprintf(fd, " bootClockTsUs: %lld\n", (long long)pendingRead.bootClockTsUs); } + dprintf(fd, " bind: %llds ago (delay: %llds)\n", + (long long)(elapsedMcs(mPreviousBindTs, Clock::now()) / 1000000), + (long long)(mPreviousBindDelay.count() / 1000)); dprintf(fd, " }\n"); const auto& params = mParams; dprintf(fd, " dataLoaderParams: {\n"); diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 5d53bac777b5..459ed3293510 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -245,7 +245,6 @@ private: void setTargetStatusLocked(int status); bool fsmStep(); - bool fsmStep(int currentStatus, int targetStatus); void onHealthStatus(StorageHealthListener healthListener, int healthStatus); void updateHealthStatus(bool baseline = false); @@ -259,6 +258,8 @@ private: BootClockTsUs getOldestPendingReadTs(); + Milliseconds updateBindDelay(); + void registerForPendingReads(); void unregisterFromPendingReads(); @@ -276,6 +277,9 @@ private: int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED; TimePoint mTargetStatusTs = {}; + TimePoint mPreviousBindTs = {}; + Milliseconds mPreviousBindDelay = {}; + std::string mHealthPath; incfs::UniqueControl mHealthControl; struct { @@ -370,6 +374,8 @@ private: void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName, std::string&& source, std::string&& target, BindKind kind); + bool needStartDataLoaderLocked(IncFsMount& ifs); + DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params, const DataLoaderStatusListener* statusListener = nullptr, diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 25d3f77fa58b..659d6503e11c 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -70,9 +70,10 @@ public: ~RealDataLoaderManager() = default; binder::Status bindToDataLoader(MountId mountId, const content::pm::DataLoaderParamsParcel& params, + int bindDelayMs, const sp<content::pm::IDataLoaderStatusListener>& listener, bool* _aidl_return) const final { - return mInterface->bindToDataLoader(mountId, params, listener, _aidl_return); + return mInterface->bindToDataLoader(mountId, params, bindDelayMs, listener, _aidl_return); } binder::Status getDataLoader(MountId mountId, sp<content::pm::IDataLoader>* _aidl_return) const final { diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index 71fd3acc6489..d60035a9274d 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -63,7 +63,7 @@ class DataLoaderManagerWrapper { public: virtual ~DataLoaderManagerWrapper() = default; virtual binder::Status bindToDataLoader( - MountId mountId, const content::pm::DataLoaderParamsParcel& params, + MountId mountId, const content::pm::DataLoaderParamsParcel& params, int bindDelayMs, const sp<content::pm::IDataLoaderStatusListener>& listener, bool* result) const = 0; virtual binder::Status getDataLoader(MountId mountId, sp<content::pm::IDataLoader>* result) const = 0; diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 8713f9d3d821..ab491efe8583 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -208,8 +208,9 @@ public: EXPECT_TRUE(mDataLoaderHolder != nullptr); } - MOCK_CONST_METHOD4(bindToDataLoader, + MOCK_CONST_METHOD5(bindToDataLoader, binder::Status(int32_t mountId, const DataLoaderParamsParcel& params, + int bindDelayMs, const sp<IDataLoaderStatusListener>& listener, bool* _aidl_return)); MOCK_CONST_METHOD2(getDataLoader, @@ -217,11 +218,11 @@ public: MOCK_CONST_METHOD1(unbindFromDataLoader, binder::Status(int32_t mountId)); void bindToDataLoaderSuccess() { - ON_CALL(*this, bindToDataLoader(_, _, _, _)) + ON_CALL(*this, bindToDataLoader(_, _, _, _, _)) .WillByDefault(Invoke(this, &MockDataLoaderManager::bindToDataLoaderOk)); } void bindToDataLoaderFails() { - ON_CALL(*this, bindToDataLoader(_, _, _, _)) + ON_CALL(*this, bindToDataLoader(_, _, _, _, _)) .WillByDefault(Return( (binder::Status::fromExceptionCode(1, String8("failed to prepare"))))); } @@ -234,6 +235,7 @@ public: .WillByDefault(Invoke(this, &MockDataLoaderManager::unbindFromDataLoaderOk)); } binder::Status bindToDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params, + int bindDelayMs, const sp<IDataLoaderStatusListener>& listener, bool* _aidl_return) { mId = mountId; @@ -245,6 +247,40 @@ public: } return binder::Status::ok(); } + binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId, + const DataLoaderParamsParcel& params, + int bindDelayMs, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) { + CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs; + return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); + } + binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId, + const DataLoaderParamsParcel& params, + int bindDelayMs, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) { + CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs; + return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); + } + binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId, + const DataLoaderParamsParcel& params, + int bindDelayMs, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) { + CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs; + return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); + } + binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId, + const DataLoaderParamsParcel& params, + int bindDelayMs, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) { + CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11) + << bindDelayMs; + return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); + } + binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) { *_aidl_return = mDataLoader; return binder::Status::ok(); @@ -261,6 +297,9 @@ public: void setDataLoaderStatusUnavailable() { mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE); } + void setDataLoaderStatusUnrecoverable() { + mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE); + } binder::Status unbindFromDataLoaderOk(int32_t id) { if (mDataLoader) { if (auto status = mDataLoader->destroy(id); !status.isOk()) { @@ -676,7 +715,7 @@ protected: TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { mVold->mountIncFsFails(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, @@ -686,7 +725,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) { mVold->mountIncFsInvalidControlParcel(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0); TemporaryDir tempDir; int storageId = @@ -698,7 +737,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) { mVold->mountIncFsSuccess(); mIncFs->makeFileFails(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; @@ -712,7 +751,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); mVold->bindMountFails(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; @@ -727,7 +766,7 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->bindToDataLoaderFails(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0); EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(0); EXPECT_CALL(*mDataLoader, start(_)).Times(0); @@ -755,11 +794,11 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { mIncrementalService->deleteStorage(storageId); } -TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) { - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2); +TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) { + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); - EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2); - EXPECT_CALL(*mDataLoader, start(_)).Times(2); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6); + EXPECT_CALL(*mDataLoader, start(_)).Times(6); EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; @@ -768,13 +807,38 @@ TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) { IncrementalService::CreateOptions::CreateNew); ASSERT_GE(storageId, 0); mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {}); + // Simulated crash/other connection breakage. + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay)); mDataLoaderManager->setDataLoaderStatusDestroyed(); } TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { mDataLoader->initializeCreateOkNoStatus(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); EXPECT_CALL(*mDataLoader, start(_)).Times(1); @@ -793,7 +857,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) { mDataLoader->initializeCreateOkNoStatus(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); EXPECT_CALL(*mDataLoader, start(_)).Times(1); @@ -811,7 +875,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) { TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) { mDataLoader->initializeCreateOkNoStatus(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); EXPECT_CALL(*mDataLoader, start(_)).Times(0); @@ -827,12 +891,30 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) { mDataLoaderManager->setDataLoaderStatusUnavailable(); } +TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnrecoverable) { + mDataLoader->initializeCreateOkNoStatus(); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoader, start(_)).Times(0); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, + IncrementalService::CreateOptions::CreateNew); + ASSERT_GE(storageId, 0); + ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, + {}, {})); + mDataLoaderManager->setDataLoaderStatusUnrecoverable(); +} + TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { mIncFs->waitForPendingReadsSuccess(); mIncFs->openMountSuccess(); mDataLoader->initializeCreateOkNoStatus(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(2); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2); EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2); EXPECT_CALL(*mDataLoader, start(_)).Times(0); @@ -856,7 +938,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) { mIncFs->openMountSuccess(); - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); EXPECT_CALL(*mDataLoader, start(_)).Times(1); @@ -1406,7 +1488,7 @@ static ErrorCode checkPerUidTimeoutsEmpty( } TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) { - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); EXPECT_CALL(*mDataLoader, start(_)).Times(1); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 97e75828ed91..a0e5c5deef61 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1291,7 +1291,6 @@ public final class SystemServer implements Dumpable { t.traceBegin("startOtherServices"); final Context context = mSystemContext; - VibratorService vibrator = null; DynamicSystemService dynamicSystem = null; IStorageManager storageManager = null; NetworkManagementService networkManagement = null; @@ -1417,11 +1416,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); t.traceEnd(); - t.traceBegin("StartVibratorService"); - vibrator = new VibratorService(context); - ServiceManager.addService("vibrator", vibrator); - t.traceEnd(); - t.traceBegin("StartDynamicSystemService"); dynamicSystem = new DynamicSystemService(context); ServiceManager.addService("dynamic_system", dynamicSystem); @@ -2490,14 +2484,6 @@ public final class SystemServer implements Dumpable { // It is now time to start up the app processes... - t.traceBegin("MakeVibratorServiceReady"); - try { - vibrator.systemReady(); - } catch (Throwable e) { - reportWtf("making Vibrator Service ready", e); - } - t.traceEnd(); - t.traceBegin("MakeLockSettingsServiceReady"); if (lockSettings != null) { try { 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 cbebe6984794..2219d477630e 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -38,7 +38,6 @@ 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; @@ -874,8 +873,7 @@ public class BackupManagerServiceRoboTest { SecurityException.class, () -> backupManagerService.requestBackup( - mUserTwoId, packages, observer, monitor, 0, - OperationType.BACKUP)); + mUserTwoId, packages, observer, monitor, 0)); } /** @@ -893,11 +891,9 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); - backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); - verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -910,11 +906,9 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0); - verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -927,11 +921,9 @@ public class BackupManagerServiceRoboTest { IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); - verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0, - OperationType.BACKUP); + verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0); } /** @@ -1092,11 +1084,9 @@ public class BackupManagerServiceRoboTest { registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT); - verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); } /** Test that the backup service does not route methods for non-registered users. */ @@ -1106,11 +1096,9 @@ public class BackupManagerServiceRoboTest { registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT); - verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT, - OperationType.BACKUP); + verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); } /** 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/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java index f0d7006633a2..da3d1d6187fc 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java @@ -198,6 +198,10 @@ public class VibratorManagerServiceTest { return mVibratorProviders.get(vibratorId) .newVibratorController(vibratorId, listener); } + + @Override + void addService(String name, IBinder service) { + } }); service.systemReady(); return service; diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java deleted file mode 100644 index 633957a8b13a..000000000000 --- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +++ /dev/null @@ -1,757 +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; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.AppOpsManager; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.PackageManagerInternal; -import android.hardware.input.IInputManager; -import android.hardware.input.InputManager; -import android.hardware.vibrator.IVibrator; -import android.media.AudioAttributes; -import android.media.AudioManager; -import android.os.Handler; -import android.os.IBinder; -import android.os.IVibratorStateListener; -import android.os.Looper; -import android.os.PowerManagerInternal; -import android.os.PowerSaveState; -import android.os.Process; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.VibrationAttributes; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.os.VibratorInfo; -import android.os.test.TestLooper; -import android.platform.test.annotations.Presubmit; -import android.provider.Settings; -import android.view.InputDevice; - -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 org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * Tests for {@link VibratorService}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:VibratorServiceTest - */ -@Presubmit -public class VibratorServiceTest { - - private static final int TEST_TIMEOUT_MILLIS = 1_000; - private static final int UID = Process.ROOT_UID; - private static final int VIBRATOR_ID = 1; - private static final String PACKAGE_NAME = "package"; - private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); - private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() - .setBatterySaverEnabled(true).build(); - private static final VibrationAttributes ALARM_ATTRS = - new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build(); - private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS = - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_TOUCH).build(); - private static final VibrationAttributes NOTIFICATION_ATTRS = - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_NOTIFICATION).build(); - private static final VibrationAttributes RINGTONE_ATTRS = - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_RINGTONE).build(); - - @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); - - @Mock private PackageManagerInternal mPackageManagerInternalMock; - @Mock private PowerManagerInternal mPowerManagerInternalMock; - @Mock private AppOpsManager mAppOpsManagerMock; - @Mock private IVibratorStateListener mVibratorStateListenerMock; - @Mock private IInputManager mIInputManagerMock; - @Mock private IBinder mVibratorStateListenerBinderMock; - - private TestLooper mTestLooper; - private ContextWrapper mContextSpy; - private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; - private FakeVibrator mFakeVibrator; - private FakeVibratorControllerProvider mVibratorProvider; - - @Before - public void setUp() throws Exception { - mTestLooper = new TestLooper(); - mFakeVibrator = new FakeVibrator(); - mVibratorProvider = new FakeVibratorControllerProvider(mTestLooper.getLooper()); - mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); - InputManager inputManager = InputManager.resetInstance(mIInputManagerMock); - - ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); - when(mContextSpy.getContentResolver()).thenReturn(contentResolver); - when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator); - when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager); - when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock); - when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock); - when(mPackageManagerInternalMock.getSystemUiServiceComponent()) - .thenReturn(new ComponentName("", "")); - doAnswer(invocation -> { - mRegisteredPowerModeListener = invocation.getArgument(0); - return null; - }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any()); - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); - - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - - addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock); - addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); - } - - @After - public void tearDown() throws Exception { - InputManager.clearInstance(); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.removeServiceForTest(PowerManagerInternal.class); - } - - private VibratorService createService() { - VibratorService service = new VibratorService(mContextSpy, - new VibratorService.Injector() { - @Override - VibratorController createVibratorController( - VibratorController.OnVibrationCompleteListener listener) { - return mVibratorProvider.newVibratorController(VIBRATOR_ID, listener); - } - - @Override - Handler createHandler(Looper looper) { - return new Handler(mTestLooper.getLooper()); - } - - @Override - void addService(String name, IBinder service) { - // ignore - } - }); - service.systemReady(); - return service; - } - - @Test - public void createService_initializesNativeService() { - createService(); - assertTrue(mVibratorProvider.isInitialized()); - } - - @Test - public void hasVibrator_withVibratorHalPresent_returnsTrue() { - assertTrue(createService().hasVibrator()); - } - - @Test - public void hasVibrator_withNoVibratorHalPresent_returnsFalse() { - mVibratorProvider.disableVibrators(); - assertFalse(createService().hasVibrator()); - } - - @Test - public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() { - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - assertTrue(createService().hasAmplitudeControl()); - } - - @Test - public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() { - assertFalse(createService().hasAmplitudeControl()); - } - - @Test - public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - assertTrue(createService().hasAmplitudeControl()); - } - - @Test - public void getVibratorInfo_returnsSameInfoFromNative() { - mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, - IVibrator.CAP_AMPLITUDE_CONTROL); - mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - mVibratorProvider.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK); - - VibratorInfo info = createService().getVibratorInfo(); - assertTrue(info.hasAmplitudeControl()); - assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES, - info.isEffectSupported(VibrationEffect.EFFECT_CLICK)); - assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO, - info.isEffectSupported(VibrationEffect.EFFECT_TICK)); - assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); - assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); - } - - @Test - public void vibrate_withRingtone_usesRingtoneSettings() throws Exception { - setRingerMode(AudioManager.RINGER_MODE_NORMAL); - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); - vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS); - - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); - vibrateAndWait(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS); - - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); - vibrateAndWait(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(2, effects.size()); - assertEquals(10, effects.get(0).getDuration()); - assertEquals(100, effects.get(1).getDuration()); - } - - @Test - public void vibrate_withPowerModeChange_usesLowPowerModeState() throws Exception { - VibratorService service = createService(); - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS); - vibrateAndWait(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS); - - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - vibrateAndWait(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null); - vibrateAndWait(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(3, effects.size()); - assertEquals(2, effects.get(0).getDuration()); - assertEquals(3, effects.get(1).getDuration()); - assertEquals(4, effects.get(2).getDuration()); - } - - @Test - public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() { - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - AudioAttributes audioAttributes = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build(); - VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder( - audioAttributes, effect).build(); - - vibrate(service, effect, vibrationAttributes); - - verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString()); - } - - @Test - public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { - VibratorService service = createService(); - - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build()); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_UNKNOWN).build()); - - InOrder inOrderVerifier = inOrder(mAppOpsManagerMock); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST), - anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString()); - } - - @Test - public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createOneShot(100, 128); - vibrate(service, effect, ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude() - throws Exception { - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - VibratorService service = createService(); - - vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(1, effects.size()); - assertEquals(100, effects.get(0).getDuration()); - assertEquals(Arrays.asList(128), mVibratorProvider.getAmplitudes()); - } - - @Test - public void vibrate_withOneShotAndNoAmplitudeControl_turnsVibratorOnAndIgnoresAmplitude() - throws Exception { - VibratorService service = createService(); - clearInvocations(); - - vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(1, effects.size()); - assertEquals(100, effects.get(0).getDuration()); - assertTrue(mVibratorProvider.getAmplitudes().isEmpty()); - } - - @Test - public void vibrate_withPrebaked_performsEffect() throws Exception { - mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - vibrateAndWait(service, effect, ALARM_ATTRS); - - VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked( - VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); - assertEquals(Arrays.asList(expectedEffect), mVibratorProvider.getEffects()); - } - - @Test - public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices() - throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), any(), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_enteringLowPowerMode_cancelVibration() throws Exception { - VibratorService service = createService(); - - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - } - - @Test - public void vibrate_enteringLowPowerModeAndRingtone_doNotCancelVibration() throws Exception { - VibratorService service = createService(); - - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - vibrate(service, VibrationEffect.createOneShot(1000, 100), RINGTONE_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - // Settings callback is async, so wait before checking it never got cancelled. - assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20)); - } - - @Test - public void vibrate_withSettingsChanged_doNotCancelVibration() throws Exception { - VibratorService service = createService(); - vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - - // FakeSettingsProvider don't support testing triggering ContentObserver yet. - service.updateVibrators(); - - // Settings callback is async, so wait before checking it never got cancelled. - assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20)); - } - - @Test - public void vibrate_withComposed_performsEffect() throws Exception { - mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .compose(); - vibrateAndWait(service, effect, ALARM_ATTRS); - assertEquals(Arrays.asList(effect), mVibratorProvider.getEffects()); - } - - @Test - public void vibrate_withComposedAndInputDevices_vibratesInputDevices() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .compose(); - vibrate(service, effect, ALARM_ATTRS); - InOrder inOrderVerifier = inOrder(mIInputManagerMock); - inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any()); - inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime() - throws Exception { - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createWaveform( - new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1); - vibrateAndWait(service, effect, ALARM_ATTRS); - - assertEquals(Arrays.asList(100, 200, 50), mVibratorProvider.getAmplitudes()); - assertEquals( - Arrays.asList(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)), - mVibratorProvider.getEffects()); - } - - @Test - public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createWaveform( - new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1); - vibrate(service, effect, ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception { - mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - VibratorService service = createService(); - - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - // Trigger callbacks from controller. - mTestLooper.moveTimeForward(50); - mTestLooper.dispatchAll(); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - } - - @Test - public void cancelVibrate_withDeviceVibrating_callsOff() throws Exception { - VibratorService service = createService(); - - vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - service.cancelVibrate(service); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - } - - @Test - public void registerVibratorStateListener_callbacksAreTriggered() throws Exception { - VibratorService service = createService(); - service.registerVibratorStateListener(mVibratorStateListenerMock); - - vibrateAndWait(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS); - - InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock); - // First notification done when listener is registered. - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false)); - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true)); - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false)); - inOrderVerifier.verifyNoMoreInteractions(); - } - - @Test - public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception { - VibratorService service = createService(); - - service.registerVibratorStateListener(mVibratorStateListenerMock); - - vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - service.unregisterVibratorStateListener(mVibratorStateListenerMock); - // Trigger callbacks from controller. - mTestLooper.moveTimeForward(50); - mTestLooper.dispatchAll(); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock); - // First notification done when listener is registered. - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false)); - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true)); - inOrderVerifier.verify(mVibratorStateListenerMock, atLeastOnce()).asBinder(); // unregister - inOrderVerifier.verifyNoMoreInteractions(); - } - - @Test - public void scale_withPrebaked_userIntensitySettingAsEffectStrength() throws Exception { - // Alarm vibration is always VIBRATION_INTENSITY_HIGH. - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - mVibratorProvider.setSupportedEffects( - VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_TICK, - VibrationEffect.EFFECT_DOUBLE_CLICK, - VibrationEffect.EFFECT_HEAVY_CLICK); - VibratorService service = createService(); - - vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), - NOTIFICATION_ATTRS); - vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), - HAPTIC_FEEDBACK_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS); - - List<Integer> playedStrengths = mVibratorProvider.getEffects().stream() - .map(VibrationEffect.Prebaked.class::cast) - .map(VibrationEffect.Prebaked::getEffectStrength) - .collect(Collectors.toList()); - assertEquals(Arrays.asList( - VibrationEffect.EFFECT_STRENGTH_STRONG, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, - VibrationEffect.EFFECT_STRENGTH_LIGHT), - playedStrengths); - } - - @Test - public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception { - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - VibratorService service = createService(); - - vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS); - vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS); - vibrateAndWait(service, - VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1), - HAPTIC_FEEDBACK_ATTRS); - vibrate(service, VibrationEffect.createOneShot(20, 255), RINGTONE_ATTRS); - - List<Integer> amplitudes = mVibratorProvider.getAmplitudes(); - assertEquals(3, amplitudes.size()); - // Alarm vibration is never scaled. - assertEquals(100, amplitudes.get(0).intValue()); - // Notification vibrations will be scaled with SCALE_VERY_HIGH. - assertTrue(amplitudes.get(1) > 150); - // Haptic feedback vibrations will be scaled with SCALE_LOW. - assertTrue(amplitudes.get(2) < 100 && amplitudes.get(2) > 50); - } - - @Test - public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() throws Exception { - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - - mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) - .compose(); - - vibrateAndWait(service, effect, ALARM_ATTRS); - vibrateAndWait(service, effect, NOTIFICATION_ATTRS); - vibrateAndWait(service, effect, HAPTIC_FEEDBACK_ATTRS); - vibrate(service, effect, RINGTONE_ATTRS); - - List<VibrationEffect.Composition.PrimitiveEffect> primitives = - mVibratorProvider.getEffects().stream() - .map(VibrationEffect.Composed.class::cast) - .map(VibrationEffect.Composed::getPrimitiveEffects) - .flatMap(List::stream) - .collect(Collectors.toList()); - - // Ringtone vibration is off, so only the other 3 are propagated to native. - assertEquals(6, primitives.size()); - - // Alarm vibration is never scaled. - assertEquals(1f, primitives.get(0).scale, /* delta= */ 1e-2); - assertEquals(0.5f, primitives.get(1).scale, /* delta= */ 1e-2); - - // Notification vibrations will be scaled with SCALE_VERY_HIGH. - assertEquals(1f, primitives.get(2).scale, /* delta= */ 1e-2); - assertTrue(0.7 < primitives.get(3).scale); - - // Haptic feedback vibrations will be scaled with SCALE_LOW. - assertTrue(0.5 < primitives.get(4).scale); - assertTrue(0.5 > primitives.get(5).scale); - } - - private void vibrate(VibratorService service, VibrationEffect effect, - VibrationAttributes attrs) { - service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service); - } - - private void vibrateAndWait(VibratorService service, VibrationEffect effect, - VibrationAttributes attrs) throws Exception { - CountDownLatch startedCount = new CountDownLatch(1); - CountDownLatch finishedCount = new CountDownLatch(1); - service.registerVibratorStateListener(new IVibratorStateListener() { - @Override - public void onVibrating(boolean vibrating) { - if (vibrating) { - startedCount.countDown(); - } else if (startedCount.getCount() == 0) { - finishedCount.countDown(); - } - } - - @Override - public IBinder asBinder() { - return mock(IBinder.class); - } - }); - - mTestLooper.startAutoDispatch(); - service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service); - assertTrue(startedCount.await(1, TimeUnit.SECONDS)); - assertTrue(finishedCount.await(1, TimeUnit.SECONDS)); - mTestLooper.stopAutoDispatchAndIgnoreExceptions(); - } - - private InputDevice createInputDeviceWithVibrator(int id) { - return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0, - null, /* hasVibrator= */ true, false, false, false /* hasSensor */, - false /* hasBattery */); - } - - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - - private void setRingerMode(int ringerMode) { - AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class); - audioManager.setRingerModeInternal(ringerMode); - assertEquals(ringerMode, audioManager.getRingerModeInternal()); - } - - private void setUserSetting(String settingName, int value) { - Settings.System.putIntForUser( - mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); - } - - private void setGlobalSetting(String settingName, int value) { - Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value); - } - - private boolean waitUntil(Predicate<VibratorService> predicate, - VibratorService service, long timeout) throws InterruptedException { - long timeoutTimestamp = SystemClock.uptimeMillis() + timeout; - boolean predicateResult = false; - while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) { - Thread.sleep(10); - predicateResult = predicate.test(service); - } - return predicateResult; - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java index fdf509504837..a946534f4ecd 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import android.content.Context; @@ -30,15 +28,12 @@ import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.StateResidencyResult; import android.power.PowerStatsInternal; import android.util.SparseArray; -import android.util.SparseLongArray; import androidx.test.InstrumentationRegistry; import com.android.internal.os.BatteryStatsImpl; -import com.android.internal.power.MeasuredEnergyArray; import org.junit.Before; -import org.junit.Test; import java.util.concurrent.CompletableFuture; @@ -63,44 +58,6 @@ public class BatteryExternalStatsWorkerTest { mBatteryStatsImpl); } - @Test - public void getEnergyConsumptionData() { - SparseLongArray expectSubsystems = new SparseLongArray(); - // Add some energy consumers used by BatteryExternalStatsWorker. - final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0, - "display"); - mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345); - expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345); - - // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker. - // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW. - final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer"); - mPowerStatsInternal.incrementEnergyConsumption(someId, 34567); - - // Inform BESW that PowerStatsInternal is ready to query - mBatteryExternalStatsWorker.systemServicesReady(); - - MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData(); - - assertEquals(expectSubsystems.size(), energies.size()); - final int size = expectSubsystems.size(); - - for (int i = 0; i < size; i++) { - int subsystem = expectSubsystems.keyAt(i); - // find the subsystem in the returned MeasuredEnergyArray - int subsystemIndex = -1; - for (int j = 0; j < size; j++) { - if (subsystem == energies.getSubsystem(i)) { - subsystemIndex = i; - break; - } - } - assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1, - subsystemIndex); - assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex)); - } - } - public class TestInjector extends BatteryExternalStatsWorker.Injector { public TestInjector(Context context) { super(context); diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java index 67d379a47420..1efce39e00fa 100644 --- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java +++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java @@ -16,18 +16,23 @@ package com.android.server.am; +import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerAttribution; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; +import android.util.SparseArray; import android.util.SparseLongArray; import androidx.test.filters.SmallTest; -import com.android.internal.power.MeasuredEnergyArray; +import com.android.server.am.MeasuredEnergySnapshot.MeasuredEnergyDeltaData; -import org.junit.Before; import org.junit.Test; /** @@ -38,134 +43,198 @@ import org.junit.Test; */ @SmallTest public final class MeasuredEnergySnapshotTest { - private static final int NUMBER_SUBSYSTEMS = 3; - private static final int SUBSYSTEM_DISPLAY = 0; - private static final int SUBSYSTEM_NEVER_USED = 1; - private static final int SUBSYSTEM_CATAPULT = 2; - - private MeasuredEnergySnapshot mSnapshot; - - // Basic MeasuredEnergyArray that supports all the subsystems. Out of order on purpose. - private final int[] mAllSubsystems = - {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT, SUBSYSTEM_NEVER_USED}; - // E.g. mAllSubsystems[mSubsystemIndices[SUBSYSTEM_CATAPULT]]=SUBSYSTEM_CATAPULT - private final int[] mSubsystemIndices = {0, 2, 1}; - private final long[] mCurrentSubsystemEnergyUJ = {111, 0, 0}; - private final MeasuredEnergyArray mOmniEnergyArray = new MeasuredEnergyArray() { - @Override - public int getSubsystem(int index) { - return mAllSubsystems[index]; - } - - @Override - public long getEnergy(int index) { - return mCurrentSubsystemEnergyUJ[index]; - } - - @Override - public int size() { - return mAllSubsystems.length; - } + private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer( + 0, 0, EnergyConsumerType.DISPLAY, "Display"); + private static final EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer( + 47, 0, EnergyConsumerType.OTHER, "GPU"); + private static final EnergyConsumer CONSUMER_OTHER_1 = createEnergyConsumer( + 1, 1, EnergyConsumerType.OTHER, "HPU"); + private static final EnergyConsumer CONSUMER_OTHER_2 = createEnergyConsumer( + 436, 2, EnergyConsumerType.OTHER, "IPU"); + + private static final SparseArray<EnergyConsumer> ALL_ID_CONSUMER_MAP = createIdToConsumerMap( + CONSUMER_DISPLAY, CONSUMER_OTHER_0, CONSUMER_OTHER_1, CONSUMER_OTHER_2); + private static final SparseArray<EnergyConsumer> SOME_ID_CONSUMER_MAP = createIdToConsumerMap( + CONSUMER_DISPLAY); + + // Elements in each results are purposefully out of order. + private static final EnergyConsumerResult[] RESULTS_0 = new EnergyConsumerResult[] { + createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}), + createEnergyConsumerResult(CONSUMER_DISPLAY.id, 14, null, null), + createEnergyConsumerResult(CONSUMER_OTHER_1.id, 0, null, null), + // No CONSUMER_OTHER_2 }; - private final MeasuredEnergyArray mJustDisplayEnergyArray = new MeasuredEnergyArray() { - @Override - public int getSubsystem(int index) { - return mAllSubsystems[0]; - } - - @Override - public long getEnergy(int index) { - return mCurrentSubsystemEnergyUJ[0]; - } - - @Override - public int size() { - return 1; - } + private static final EnergyConsumerResult[] RESULTS_1 = new EnergyConsumerResult[] { + createEnergyConsumerResult(CONSUMER_DISPLAY.id, 24, null, null), + createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}), + createEnergyConsumerResult(CONSUMER_OTHER_2.id, 12, new int[] {6}, new long[] {10}), + createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null), + }; + private static final EnergyConsumerResult[] RESULTS_2 = new EnergyConsumerResult[] { + createEnergyConsumerResult(CONSUMER_DISPLAY.id, 36, null, null), + // No CONSUMER_OTHER_0 + // No CONSUMER_OTHER_1 + // No CONSUMER_OTHER_2 + }; + private static final EnergyConsumerResult[] RESULTS_3 = new EnergyConsumerResult[] { + // No CONSUMER_DISPLAY + createEnergyConsumerResult(CONSUMER_OTHER_2.id, 13, new int[] {6}, new long[] {10}), + createEnergyConsumerResult( + CONSUMER_OTHER_0.id, 190, new int[] {2, 3, 47, 7}, new long[] {9, 18, 14, 6}), + createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null), + }; + private static final EnergyConsumerResult[] RESULTS_4 = new EnergyConsumerResult[] { + createEnergyConsumerResult(CONSUMER_DISPLAY.id, 43, null, null), + createEnergyConsumerResult( + CONSUMER_OTHER_0.id, 290, new int[] {7, 47, 3, 2}, new long[] {6, 14, 18, 11}), + // No CONSUMER_OTHER_1 + createEnergyConsumerResult(CONSUMER_OTHER_2.id, 165, new int[] {6, 47}, new long[] {10, 8}), }; - @Before - public void setUp() { - mSnapshot = new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, mOmniEnergyArray); + @Test + public void testUpdateAndGetDelta_empty() { + final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP); + assertNull(snapshot.updateAndGetDelta(null)); + assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0])); } @Test public void testUpdateAndGetDelta() { - SparseLongArray result; - - // Increment DISPLAY by 15 - incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 15); - result = mSnapshot.updateAndGetDelta(mOmniEnergyArray); - assertEquals(1, result.size()); - assertEquals(15, result.get(SUBSYSTEM_DISPLAY)); - - // Increment DISPLAY by 7 - // Increment CATAPULT by 5. But do NOT include (pull) it in the passed in energy array. - incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 7); - incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 5); - result = mSnapshot.updateAndGetDelta(mJustDisplayEnergyArray); // Just pull display. - assertEquals(1, result.size()); - assertEquals(7, result.get(SUBSYSTEM_DISPLAY)); - - // Increment CATAPULT by 64 (in addition to the previous increase of 5) - incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 64); - result = mSnapshot.updateAndGetDelta(mOmniEnergyArray); - assertEquals(1, result.size()); - assertEquals(5 + 64, result.get(SUBSYSTEM_CATAPULT)); - - // Do nothing - result = mSnapshot.updateAndGetDelta(mOmniEnergyArray); - assertEquals("0 results should not appear at all", 0, result.size()); - - // Increment DISPLAY by 42 - incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 42); - result = mSnapshot.updateAndGetDelta(mOmniEnergyArray); - assertEquals(1, result.size()); - assertEquals(42, result.get(SUBSYSTEM_DISPLAY)); - - // Increment DISPLAY by 106 and CATAPULT by 13 - incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 106); - incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 13); - result = mSnapshot.updateAndGetDelta(mOmniEnergyArray); - assertEquals(2, result.size()); - assertEquals(106, result.get(SUBSYSTEM_DISPLAY)); - assertEquals(13, result.get(SUBSYSTEM_CATAPULT)); + final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP); + + // results0 + MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0); + if (delta != null) { // null is fine here. If non-null, it better be uninteresting though. + assertEquals(UNAVAILABLE, delta.displayEnergyUJ); + assertNull(delta.otherTotalEnergyUJ); + assertNull(delta.otherUidEnergiesUJ); + } + + // results1 + delta = snapshot.updateAndGetDelta(RESULTS_1); + assertNotNull(delta); + assertEquals(24 - 14, delta.displayEnergyUJ); + + assertNotNull(delta.otherTotalEnergyUJ); + assertEquals(90 - 90, delta.otherTotalEnergyUJ[0]); + assertEquals(12_000 - 0, delta.otherTotalEnergyUJ[1]); + assertEquals(0, delta.otherTotalEnergyUJ[2]); // First good pull. Treat delta as 0. + + assertNotNull(delta.otherUidEnergiesUJ); + assertNullOrEmpty(delta.otherUidEnergiesUJ[0]); // No change in uid energies + assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); + assertNullOrEmpty(delta.otherUidEnergiesUJ[2]); + + // results2 + delta = snapshot.updateAndGetDelta(RESULTS_2); + assertNotNull(delta); + assertEquals(36 - 24, delta.displayEnergyUJ); + assertNull(delta.otherUidEnergiesUJ); + assertNull(delta.otherTotalEnergyUJ); + + // results3 + delta = snapshot.updateAndGetDelta(RESULTS_3); + assertNotNull(delta); + assertEquals(UNAVAILABLE, delta.displayEnergyUJ); + + assertNotNull(delta.otherTotalEnergyUJ); + assertEquals(190 - 90, delta.otherTotalEnergyUJ[0]); + assertEquals(12_000 - 12_000, delta.otherTotalEnergyUJ[1]); + assertEquals(13 - 12, delta.otherTotalEnergyUJ[2]); + + assertNotNull(delta.otherUidEnergiesUJ); + assertEquals(3, delta.otherUidEnergiesUJ[0].size()); + assertEquals(9 - 0, delta.otherUidEnergiesUJ[0].get(2)); + assertEquals(18 - 13, delta.otherUidEnergiesUJ[0].get(3)); + assertEquals(6 - 0, delta.otherUidEnergiesUJ[0].get(7)); + assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); + assertNullOrEmpty(delta.otherUidEnergiesUJ[2]); + + // results4 + delta = snapshot.updateAndGetDelta(RESULTS_4); + assertNotNull(delta); + assertEquals(43 - 36, delta.displayEnergyUJ); + + assertNotNull(delta.otherTotalEnergyUJ); + assertEquals(290 - 190, delta.otherTotalEnergyUJ[0]); + assertEquals(0, delta.otherTotalEnergyUJ[1]); // Not present (e.g. missing data) + assertEquals(165 - 13, delta.otherTotalEnergyUJ[2]); + + assertNotNull(delta.otherUidEnergiesUJ); + assertEquals(1, delta.otherUidEnergiesUJ[0].size()); + assertEquals(11 - 9, delta.otherUidEnergiesUJ[0].get(2)); + assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); // Not present + assertEquals(1, delta.otherUidEnergiesUJ[2].size()); + assertEquals(8, delta.otherUidEnergiesUJ[2].get(47)); } - private void incrementEnergyOfSubsystem(int subsystem, long energy) { - mCurrentSubsystemEnergyUJ[mSubsystemIndices[subsystem]] += energy; + /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */ + @Test + public void testUpdateAndGetDelta_some() { + final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP); + + // results0 + MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0); + if (delta != null) { // null is fine here. If non-null, it better be uninteresting though. + assertEquals(UNAVAILABLE, delta.displayEnergyUJ); + assertNull(delta.otherTotalEnergyUJ); + assertNull(delta.otherUidEnergiesUJ); + } + + // results1 + delta = snapshot.updateAndGetDelta(RESULTS_1); + assertNotNull(delta); + assertEquals(24 - 14, delta.displayEnergyUJ); + assertNull(delta.otherTotalEnergyUJ); // Although in the results, they're not in the idMap + assertNull(delta.otherUidEnergiesUJ); } @Test - public void testUpdateAndGetDelta_null() { - assertNull(mSnapshot.updateAndGetDelta(null)); + public void testGetNumOtherOrdinals() { + final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP); + assertEquals(3, snapshot.getNumOtherOrdinals()); } @Test - public void testHasSubsystem() { - // Setup MeasuredEnergySnapshot which reported some of the subsystems. - final int[] subsystems = {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT}; - MeasuredEnergyArray measuredEnergyArray = new MeasuredEnergyArray() { - @Override - public int getSubsystem(int index) { - return subsystems[index]; - } + public void testGetNumOtherOrdinals_none() { + final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP); + assertEquals(0, snapshot.getNumOtherOrdinals()); + } - @Override - public long getEnergy(int index) { - return 0; // Irrelevant for this test. - } + private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) { + final EnergyConsumer ec = new EnergyConsumer(); + ec.id = id; + ec.ordinal = ord; + ec.type = type; + ec.name = name; + return ec; + } - @Override - public int size() { - return subsystems.length; + private static SparseArray<EnergyConsumer> createIdToConsumerMap(EnergyConsumer ... ecs) { + final SparseArray<EnergyConsumer> map = new SparseArray<>(); + for (EnergyConsumer ec : ecs) { + map.put(ec.id, ec); + } + return map; + } + + private static EnergyConsumerResult createEnergyConsumerResult( + int id, long energyUWs, int[] uids, long[] uidEnergies) { + final EnergyConsumerResult ecr = new EnergyConsumerResult(); + ecr.id = id; + ecr.energyUWs = energyUWs; + if (uids != null) { + ecr.attribution = new EnergyConsumerAttribution[uids.length]; + for (int i = 0; i < uids.length; i++) { + ecr.attribution[i] = new EnergyConsumerAttribution(); + ecr.attribution[i].uid = uids[i]; + ecr.attribution[i].energyUWs = uidEnergies[i]; } - }; - final MeasuredEnergySnapshot snapshot = - new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, measuredEnergyArray); + } + return ecr; + } - assertTrue(snapshot.hasSubsystem(SUBSYSTEM_DISPLAY)); - assertTrue(snapshot.hasSubsystem(SUBSYSTEM_CATAPULT)); - assertFalse(snapshot.hasSubsystem(SUBSYSTEM_NEVER_USED)); + private void assertNullOrEmpty(SparseLongArray a) { + if (a != null) assertEquals("Array should be null or empty", 0, a.size()); } } 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 b98f0257d7b7..af11fe125a4e 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -23,6 +23,7 @@ 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; @@ -30,6 +31,7 @@ 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; @@ -56,6 +58,7 @@ public class UserBackupManagerServiceTest { @Mock IBackupObserver mBackupObserver; @Mock PackageManager mPackageManager; @Mock TransportClient mTransportClient; + @Mock IBackupTransport mBackupTransport; @Mock BackupEligibilityRules mBackupEligibilityRules; @@ -132,6 +135,29 @@ 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/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/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/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java index 25dbc6b6cd51..33ea7108a705 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java @@ -45,6 +45,7 @@ import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.os.ResultReceiver; import android.os.ShellCallback; import android.platform.test.annotations.Presubmit; @@ -89,7 +90,8 @@ public class LockSettingsShellCommandTest { MockitoAnnotations.initMocks(this); final Context context = InstrumentationRegistry.getTargetContext(); mUserId = ActivityManager.getCurrentUser(); - mCommand = new LockSettingsShellCommand(mLockPatternUtils); + mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0, + Process.SHELL_UID); when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true); } 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/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index 22020ad45666..bc84e350a329 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -96,7 +96,7 @@ public class DexoptOptionsTests { int[] reasons = new int[] { PackageManagerService.REASON_FIRST_BOOT, - PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_POST_BOOT, PackageManagerService.REASON_INSTALL, PackageManagerService.REASON_BACKGROUND_DEXOPT, PackageManagerService.REASON_AB_OTA, diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index b28994cafa0f..55748366c1ba 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -28,7 +28,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.timedetector.ExternalTimeSuggestion; +import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index 6dcfb42f7f08..daa1b25de22f 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -27,7 +27,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.app.timedetector.ExternalTimeSuggestion; +import android.app.time.ExternalTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; 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..b13f80b6f490 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.when; import android.app.INotificationManager; import android.content.ComponentName; +import android.content.pm.VersionedPackage; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -83,11 +84,10 @@ public class NotificationListenersTest extends UiServiceTestCase { String xml = "<req_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>"; @@ -103,8 +103,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 +135,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); @@ -157,8 +160,9 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test public void testOnUserUnlocked() { // 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); // new service exists or backfilling on upgrade to S @@ -188,7 +192,7 @@ public class NotificationListenersTest extends UiServiceTestCase { .isEqualTo(4); assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) .getDisallowedPackages()) - .contains("something"); + .contains(a1); assertThat(mListeners.getNotificationListenerFilter( Pair.create(si.getComponentName(), 0)).getTypes()) @@ -205,8 +209,9 @@ public class NotificationListenersTest extends UiServiceTestCase { @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 +229,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 +245,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/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/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..83b30a9747d7 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, 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/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 3b06fd3d4ea1..170ed3eff614 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -2024,7 +2024,7 @@ public abstract class ConnectionService extends Service { boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean( TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false); boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean( - PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false); + PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true); Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, " + " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming, diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index 99f2e5ee0755..c6757fba4d53 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -646,7 +646,7 @@ public class Annotation { TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, - TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE}) + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED}) public @interface OverrideNetworkType {} /** diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java index 1fcb504e7895..5b5570b27a9b 100644 --- a/telephony/java/android/telephony/TelephonyDisplayInfo.java +++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java @@ -66,9 +66,26 @@ public final class TelephonyDisplayInfo implements Parcelable { * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC) * capability or is currently connected to the secondary * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands. + * @deprecated Use{@link #OVERRIDE_NETWORK_TYPE_NR_ADVANCED} instead. */ + @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; + /** + * Override network type when the device is connected NR cellular network and the data rate is + * higher than the generic 5G date rate. + * Including but not limited to + * <ul> + * <li>The device is connected to the NR cellular network on millimeter wave bands. </li> + * <li>The device is connected to the specific network which the carrier is using + * proprietary means to provide a faster overall data connection than would be otherwise + * possible. This may include using other bands unique to the carrier, or carrier + * aggregation, for example.</li> + * </ul> + * One of the use case is that UX can show a different icon, for example, "5G+" + */ + public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; + @NetworkType private final int mNetworkType; @@ -169,7 +186,7 @@ public final class TelephonyDisplayInfo implements Parcelable { case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA"; case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO"; case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA"; - case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE"; + case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE"; default: return "UNKNOWN"; } } diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index d1d6a26790fd..0f920b36cffd 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -16,8 +16,11 @@ package com.android.framework.permission.tests; +import android.content.Context; import android.os.Binder; -import android.os.IVibratorService; +import android.os.CombinedVibrationEffect; +import android.os.IBinder; +import android.os.IVibratorManagerService; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -32,27 +35,28 @@ import junit.framework.TestCase; * Verify that Hardware apis cannot be called without required permissions. */ @SmallTest -public class VibratorServicePermissionTest extends TestCase { +public class VibratorManagerServicePermissionTest extends TestCase { - private IVibratorService mVibratorService; + private IVibratorManagerService mVibratorService; @Override protected void setUp() throws Exception { - mVibratorService = IVibratorService.Stub.asInterface( - ServiceManager.getService("vibrator")); + mVibratorService = IVibratorManagerService.Stub.asInterface( + ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE)); } /** - * Test that calling {@link android.os.IVibratorService#vibrate(long)} requires permissions. + * Test that calling {@link android.os.IVibratorManagerService#vibrate(int, String, + * CombinedVibrationEffect, VibrationAttributes, String, IBinder)} requires permissions. * <p>Tests permission: - * {@link android.Manifest.permission#VIBRATE} - * @throws RemoteException + * {@link android.Manifest.permission#VIBRATE} */ public void testVibrate() throws RemoteException { try { - final VibrationEffect effect = - VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE); - final VibrationAttributes attrs = new VibrationAttributes.Builder() + CombinedVibrationEffect effect = + CombinedVibrationEffect.createSynced( + VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); + VibrationAttributes attrs = new VibrationAttributes.Builder() .setUsage(VibrationAttributes.USAGE_ALARM) .build(); mVibratorService.vibrate(Process.myUid(), null, effect, attrs, @@ -64,10 +68,10 @@ public class VibratorServicePermissionTest extends TestCase { } /** - * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions. + * Test that calling {@link android.os.IVibratorManagerService#cancelVibrate(IBinder)} requires + * permissions. * <p>Tests permission: - * {@link android.Manifest.permission#VIBRATE} - * @throws RemoteException + * {@link android.Manifest.permission#VIBRATE} */ public void testCancelVibrate() throws RemoteException { try { |