diff options
237 files changed, 6578 insertions, 4666 deletions
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/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/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 104d933ce315..2531d818e528 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 @@ -10440,6 +10441,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 +10982,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"; @@ -31627,6 +31630,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); @@ -31641,10 +31645,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 { @@ -42031,8 +42037,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 { @@ -53584,16 +53591,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 bfdce373a47e..fdd1e6660697 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"; @@ -2631,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 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 06110319cb35..632b10f3330e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -693,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"; @@ -1371,6 +1372,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/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/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/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/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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index cc86a604c194..6f89254d9388 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"), 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/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/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/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/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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 6e41b3f4ae06..96a71b994375 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() { @@ -14454,7 +14523,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 +14546,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/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/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/public.xml b/core/res/res/values/public.xml index 46f928df323c..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"> 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/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/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/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..2391a0874bcb 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 @@ -192,9 +192,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 +211,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 +322,7 @@ public class BubbleController { } @VisibleForTesting - public Bubbles getImpl() { + public Bubbles asBubbles() { return mImpl; } 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/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/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/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/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/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/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/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/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/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/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 08cd6553e252..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> 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/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/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/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/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/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/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 25438a6f57ba..a78126e701a9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -277,6 +277,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/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/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..041a97e1d404 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -89,7 +89,6 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.os.UserManager; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; @@ -226,7 +225,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; @@ -623,7 +621,6 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private KeyguardUserSwitcher mKeyguardUserSwitcher; private final UserSwitcherController mUserSwitcherController; private final NetworkController mNetworkController; private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @@ -1212,9 +1209,6 @@ public class StatusBar extends SystemUI implements DemoMode, }); mNotificationPanelViewController.setUserSetupComplete(mUserSetup); - if (UserManager.get(mContext).isUserSwitcherEnabled()) { - createUserSwitcher(); - } // Set up the quick settings tile panel final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame); @@ -1441,9 +1435,6 @@ public class StatusBar extends SystemUI implements DemoMode, // TODO: Bring these out of StatusBar. mUserInfoControllerImpl.onDensityOrFontScaleChanged(); mUserSwitcherController.onDensityOrFontScaleChanged(); - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.onDensityOrFontScaleChanged(); - } mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); mHeadsUpManager.onDensityOrFontScaleChanged(); } @@ -1477,13 +1468,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected void createUserSwitcher() { - mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext, - mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher), - mNotificationShadeWindowView.findViewById(R.id.keyguard_header), - mNotificationPanelViewController); - } - private void inflateStatusBarWindow() { mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView(); StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get() @@ -3266,7 +3250,7 @@ public class StatusBar extends SystemUI implements DemoMode, mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) { mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER); - } else if (!mPulseExpansionHandler.isWakingToShadeLocked()){ + } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) { mStatusBarStateController.setState(StatusBarState.KEYGUARD); } updatePanelExpansionForKeyguard(); @@ -3565,15 +3549,15 @@ public class StatusBar extends SystemUI implements DemoMode, } return true; } + if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) { + return true; + } if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { mShadeController.animateCollapsePanels(); } return true; } - if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) { - return true; - } return false; } @@ -3622,20 +3606,8 @@ public class StatusBar extends SystemUI implements DemoMode, updateTheme(); mNavigationBarController.touchAutoDim(mDisplayId); Trace.beginSection("StatusBar#updateKeyguardState"); - if (mState == StatusBarState.KEYGUARD) { - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.setKeyguard(true, - mStatusBarStateController.fromShadeLocked()); - } - if (mStatusBarView != null) mStatusBarView.removePendingHideExpandedRunnables(); - } else { - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.setKeyguard(false, - mStatusBarStateController.goingToFullShade() || - mState == StatusBarState.SHADE_LOCKED || - mStatusBarStateController.fromShadeLocked()); - } - + if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) { + mStatusBarView.removePendingHideExpandedRunnables(); } updateDozingState(); checkBarModes(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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/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/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/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/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/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/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/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/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/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/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index baeb3fdda67d..81e90df5802b 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 @@ -496,6 +496,7 @@ public class Sensor { } void setTestHalEnabled(boolean enabled) { + Slog.w(mTag, "setTestHalEnabled: " + enabled); 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..a38da3ad70b3 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 @@ -23,86 +23,85 @@ 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.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() { + return new ISession.Stub() { @Override public void generateChallenge(int cookie, int timeoutSec) { - + Slog.w(TAG, "generateChallenge, cookie: " + cookie); } @Override public void revokeChallenge(int cookie, long challenge) { - + Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie); } @Override public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, byte enrollmentType, byte[] features, NativeHandle previewSurface) { + Slog.w(TAG, "enroll, cookie: " + cookie); return null; } @Override public ICancellationSignal authenticate(int cookie, long operationId) { + Slog.w(TAG, "authenticate, cookie: " + cookie); return null; } @Override public ICancellationSignal detectInteraction(int cookie) { + Slog.w(TAG, "detectInteraction, cookie: " + cookie); return null; } @Override public void enumerateEnrollments(int cookie) { - + Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie); } @Override public void removeEnrollments(int cookie, int[] enrollmentIds) { - + Slog.w(TAG, "removeEnrollments, cookie: " + cookie); } @Override public void getFeatures(int cookie, int enrollmentId) { - + Slog.w(TAG, "getFeatures, cookie: " + cookie); } @Override public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, byte feature, boolean enabled) { - + Slog.w(TAG, "setFeature, cookie: " + cookie); } @Override public void getAuthenticatorId(int cookie) { - + Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie); } @Override public void invalidateAuthenticatorId(int cookie) { - + Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie); } @Override public void resetLockout(int cookie, HardwareAuthToken hat) { - - } - - @Override - public IBinder asBinder() { - return new Binder(); + Slog.w(TAG, "resetLockout, cookie: " + cookie); } }; } 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..00ca8025564d 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; } @@ -95,16 +99,19 @@ public class TestHal extends IBiometricsFace.Stub { @Override public int enumerate() { + Slog.w(TAG, "enumerate"); 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 +122,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..73b59cfdf248 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 @@ -476,6 +476,7 @@ class Sensor { } void setTestHalEnabled(boolean enabled) { + Slog.w(mTag, "setTestHalEnabled, enabled"); 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..66b68eeb335b 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 @@ -22,89 +22,89 @@ 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.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() { + return new ISession.Stub() { @Override public void generateChallenge(int cookie, int timeoutSec) { - + Slog.w(TAG, "generateChallenge, cookie: " + cookie); } @Override public void revokeChallenge(int cookie, long challenge) { - + Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie); } @Override public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) { + Slog.w(TAG, "enroll, cookie: " + cookie); return null; } @Override public ICancellationSignal authenticate(int cookie, long operationId) { + Slog.w(TAG, "authenticate, cookie: " + cookie); return null; } @Override public ICancellationSignal detectInteraction(int cookie) { + Slog.w(TAG, "detectInteraction, cookie: " + cookie); return null; } @Override public void enumerateEnrollments(int cookie) { - + Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie); } @Override public void removeEnrollments(int cookie, int[] enrollmentIds) { - + Slog.w(TAG, "removeEnrollments, cookie: " + cookie); } @Override public void getAuthenticatorId(int cookie) { - + Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie); } @Override public void invalidateAuthenticatorId(int cookie) { - + Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie); } @Override public void resetLockout(int cookie, HardwareAuthToken hat) { - + Slog.w(TAG, "resetLockout, cookie: " + cookie); } @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/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java index b7aec0ed2731..57447f3a8cf7 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; } @@ -80,11 +84,13 @@ public class TestHal extends IBiometricsFingerprint.Stub { @Override public int enumerate() { + Slog.w(TAG, "Enumerate"); return 0; } @Override public int remove(int gid, int fid) { + Slog.w(TAG, "Remove"); return 0; } @@ -95,6 +101,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/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/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/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/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/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/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..0669581ad090 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -3438,6 +3438,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 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/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/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/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/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/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/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/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/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 { |