diff options
210 files changed, 6312 insertions, 1585 deletions
diff --git a/Android.mk b/Android.mk index 28adbcaf88fc..7a8b1b763b4c 100644 --- a/Android.mk +++ b/Android.mk @@ -295,9 +295,9 @@ LOCAL_SRC_FILES += \ core/java/android/printservice/IPrintService.aidl \ core/java/android/printservice/IPrintServiceClient.aidl \ core/java/android/companion/ICompanionDeviceManager.aidl \ - core/java/android/companion/ICompanionDeviceManagerService.aidl \ - core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl \ - core/java/android/companion/IOnAssociateCallback.aidl \ + core/java/android/companion/ICompanionDeviceDiscoveryService.aidl \ + core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl \ + core/java/android/companion/IFindDeviceCallback.aidl \ core/java/android/service/dreams/IDreamManager.aidl \ core/java/android/service/dreams/IDreamService.aidl \ core/java/android/service/persistentdata/IPersistentDataBlockService.aidl \ diff --git a/api/current.txt b/api/current.txt index 9df1764380f4..7465c4d1dd88 100644 --- a/api/current.txt +++ b/api/current.txt @@ -121,6 +121,7 @@ package android { field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES"; field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS"; + field public static final java.lang.String RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND"; field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS"; field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; @@ -139,6 +140,7 @@ package android { field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR"; field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT"; field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; + field public static final java.lang.String USE_DATA_IN_BACKGROUND = "android.permission.USE_DATA_IN_BACKGROUND"; field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; field public static final java.lang.String VIBRATE = "android.permission.VIBRATE"; @@ -6200,6 +6202,7 @@ package android.app.admin { method public void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); method public void clearProfileOwner(android.content.ComponentName); + method public boolean clearResetPasswordToken(android.content.ComponentName); method public void clearUserRestriction(android.content.ComponentName, java.lang.String); method public android.content.Intent createAdminSupportIntent(java.lang.String); method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int); @@ -6276,6 +6279,7 @@ package android.app.admin { method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isProfileOwnerApp(java.lang.String); method public boolean isProvisioningAllowed(java.lang.String); + method public boolean isResetPasswordTokenActive(android.content.ComponentName); method public boolean isSecurityLoggingEnabled(android.content.ComponentName); method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String); method public void lockNow(); @@ -6287,6 +6291,7 @@ package android.app.admin { method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean requestBugreport(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); + method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int); method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName); @@ -6335,6 +6340,7 @@ package android.app.admin { method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long); + method public boolean setResetPasswordToken(android.content.ComponentName, byte[]); method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName); method public void setScreenCaptureDisabled(android.content.ComponentName, boolean); method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); @@ -6568,6 +6574,7 @@ package android.app.assist { method public int getTextStyle(); method public int getTop(); method public android.graphics.Matrix getTransformation(); + method public java.lang.String getUrl(); method public int getVisibility(); method public int getWidth(); method public boolean isAccessibilityFocused(); @@ -9220,7 +9227,7 @@ package android.content { field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"; field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT"; field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY"; - field public static final java.lang.String EXTRA_QUICK_VIEW_PLAIN = "android.intent.extra.QUICK_VIEW_PLAIN"; + field public static final java.lang.String EXTRA_QUICK_VIEW_ADVANCED = "android.intent.extra.QUICK_VIEW_ADVANCED"; field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE"; field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER"; field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME"; @@ -22316,7 +22323,7 @@ package android.media { method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; method public void prepareAsync() throws java.lang.IllegalStateException; - method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; + method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException; method public void release(); method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException; @@ -22331,6 +22338,7 @@ package android.media { method public void setAuxEffectSendLevel(float); method public void setBufferingParams(android.media.BufferingParams); method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; + method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; @@ -22343,6 +22351,7 @@ package android.media { method public void setNextMediaPlayer(android.media.MediaPlayer); method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener); method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener); + method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener); method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener); method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler); method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener); @@ -22407,9 +22416,8 @@ package android.media { method public abstract void onCompletion(android.media.MediaPlayer); } - public static abstract class MediaPlayer.OnDrmConfigCallback { - ctor public MediaPlayer.OnDrmConfigCallback(); - method public void onDrmConfig(android.media.MediaPlayer); + public static abstract interface MediaPlayer.OnDrmConfigListener { + method public abstract void onDrmConfig(android.media.MediaPlayer); } public static abstract interface MediaPlayer.OnDrmInfoListener { @@ -24096,6 +24104,7 @@ package android.media.tv { field public static final java.lang.String COLUMN_AUTHOR = "author"; field public static final java.lang.String COLUMN_AVAILABILITY = "availability"; field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre"; + field public static final java.lang.String COLUMN_BROWSABLE = "browsable"; field public static final java.lang.String COLUMN_DURATION_MILLIS = "duration_millis"; field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number"; @@ -24238,11 +24247,13 @@ package android.media.tv { field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE"; field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; + field public static final java.lang.String ACTION_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED"; field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"; field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS"; field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME"; + field public static final java.lang.String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID"; field public static final int INPUT_STATE_CONNECTED = 0; // 0x0 field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 @@ -44103,6 +44114,7 @@ package android.view { field public static final int AXIS_RX = 12; // 0xc field public static final int AXIS_RY = 13; // 0xd field public static final int AXIS_RZ = 14; // 0xe + field public static final int AXIS_SCROLL = 26; // 0x1a field public static final int AXIS_SIZE = 3; // 0x3 field public static final int AXIS_THROTTLE = 19; // 0x13 field public static final int AXIS_TILT = 25; // 0x19 @@ -45653,6 +45665,7 @@ package android.view { method public abstract void setTextLines(int[], int[]); method public abstract void setTextStyle(float, int, int, int); method public abstract void setTransformation(android.graphics.Matrix); + method public abstract void setUrl(java.lang.String); method public abstract void setVisibility(int); field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1 } @@ -47329,7 +47342,7 @@ package android.view.textclassifier { public final class TextClassificationManager { method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); - method public synchronized android.view.textclassifier.TextClassifier getDefaultTextClassifier(); + method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); } public final class TextClassificationResult { diff --git a/api/system-current.txt b/api/system-current.txt index eed1718f299d..25b393c8e27c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -211,6 +211,7 @@ package android { field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS"; field public static final java.lang.String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT"; field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS"; + field public static final java.lang.String RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND"; field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS"; @@ -248,6 +249,7 @@ package android { field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final java.lang.String UPDATE_LOCK = "android.permission.UPDATE_LOCK"; field public static final java.lang.String USER_ACTIVITY = "android.permission.USER_ACTIVITY"; + field public static final java.lang.String USE_DATA_IN_BACKGROUND = "android.permission.USE_DATA_IN_BACKGROUND"; field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; field public static final java.lang.String VIBRATE = "android.permission.VIBRATE"; @@ -6405,6 +6407,7 @@ package android.app.admin { method public void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); method public void clearProfileOwner(android.content.ComponentName); + method public boolean clearResetPasswordToken(android.content.ComponentName); method public void clearUserRestriction(android.content.ComponentName, java.lang.String); method public android.content.Intent createAdminSupportIntent(java.lang.String); method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int); @@ -6495,6 +6498,7 @@ package android.app.admin { method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isProfileOwnerApp(java.lang.String); method public boolean isProvisioningAllowed(java.lang.String); + method public boolean isResetPasswordTokenActive(android.content.ComponentName); method public boolean isSecurityLoggingEnabled(android.content.ComponentName); method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String); method public void lockNow(); @@ -6509,6 +6513,7 @@ package android.app.admin { method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean requestBugreport(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); + method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int); method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName); @@ -6559,6 +6564,7 @@ package android.app.admin { method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long); + method public boolean setResetPasswordToken(android.content.ComponentName, byte[]); method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName); method public void setScreenCaptureDisabled(android.content.ComponentName, boolean); method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); @@ -6805,6 +6811,7 @@ package android.app.assist { method public int getTextStyle(); method public int getTop(); method public android.graphics.Matrix getTransformation(); + method public java.lang.String getUrl(); method public int getVisibility(); method public int getWidth(); method public boolean isAccessibilityFocused(); @@ -9664,7 +9671,7 @@ package android.content { field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"; field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT"; field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY"; - field public static final java.lang.String EXTRA_QUICK_VIEW_PLAIN = "android.intent.extra.QUICK_VIEW_PLAIN"; + field public static final java.lang.String EXTRA_QUICK_VIEW_ADVANCED = "android.intent.extra.QUICK_VIEW_ADVANCED"; field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE"; field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER"; field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME"; @@ -23959,7 +23966,7 @@ package android.media { method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; method public void prepareAsync() throws java.lang.IllegalStateException; - method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; + method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException; method public void release(); method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException; @@ -23974,6 +23981,7 @@ package android.media { method public void setAuxEffectSendLevel(float); method public void setBufferingParams(android.media.BufferingParams); method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; + method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; @@ -23986,6 +23994,7 @@ package android.media { method public void setNextMediaPlayer(android.media.MediaPlayer); method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener); method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener); + method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener); method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener); method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler); method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener); @@ -24050,9 +24059,8 @@ package android.media { method public abstract void onCompletion(android.media.MediaPlayer); } - public static abstract class MediaPlayer.OnDrmConfigCallback { - ctor public MediaPlayer.OnDrmConfigCallback(); - method public void onDrmConfig(android.media.MediaPlayer); + public static abstract interface MediaPlayer.OnDrmConfigListener { + method public abstract void onDrmConfig(android.media.MediaPlayer); } public static abstract interface MediaPlayer.OnDrmInfoListener { @@ -25885,6 +25893,7 @@ package android.media.tv { field public static final java.lang.String COLUMN_AUTHOR = "author"; field public static final java.lang.String COLUMN_AVAILABILITY = "availability"; field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre"; + field public static final java.lang.String COLUMN_BROWSABLE = "browsable"; field public static final java.lang.String COLUMN_DURATION_MILLIS = "duration_millis"; field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number"; @@ -26108,11 +26117,13 @@ package android.media.tv { field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE"; field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; + field public static final java.lang.String ACTION_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED"; field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"; field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS"; field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME"; + field public static final java.lang.String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID"; field public static final int INPUT_STATE_CONNECTED = 0; // 0x0 field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 @@ -47541,6 +47552,7 @@ package android.view { field public static final int AXIS_RX = 12; // 0xc field public static final int AXIS_RY = 13; // 0xd field public static final int AXIS_RZ = 14; // 0xe + field public static final int AXIS_SCROLL = 26; // 0x1a field public static final int AXIS_SIZE = 3; // 0x3 field public static final int AXIS_THROTTLE = 19; // 0x13 field public static final int AXIS_TILT = 25; // 0x19 @@ -49091,6 +49103,7 @@ package android.view { method public abstract void setTextLines(int[], int[]); method public abstract void setTextStyle(float, int, int, int); method public abstract void setTransformation(android.graphics.Matrix); + method public abstract void setUrl(java.lang.String); method public abstract void setVisibility(int); field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1 } @@ -50770,7 +50783,7 @@ package android.view.textclassifier { public final class TextClassificationManager { method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); - method public synchronized android.view.textclassifier.TextClassifier getDefaultTextClassifier(); + method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); } public final class TextClassificationResult { diff --git a/api/test-current.txt b/api/test-current.txt index 114060d6a116..2c2831f9ace0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -121,6 +121,7 @@ package android { field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES"; field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS"; + field public static final java.lang.String RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND"; field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS"; field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; @@ -139,6 +140,7 @@ package android { field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR"; field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT"; field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; + field public static final java.lang.String USE_DATA_IN_BACKGROUND = "android.permission.USE_DATA_IN_BACKGROUND"; field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; field public static final java.lang.String VIBRATE = "android.permission.VIBRATE"; @@ -6217,6 +6219,7 @@ package android.app.admin { method public void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); method public void clearProfileOwner(android.content.ComponentName); + method public boolean clearResetPasswordToken(android.content.ComponentName); method public void clearUserRestriction(android.content.ComponentName, java.lang.String); method public android.content.Intent createAdminSupportIntent(java.lang.String); method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int); @@ -6298,6 +6301,7 @@ package android.app.admin { method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isProfileOwnerApp(java.lang.String); method public boolean isProvisioningAllowed(java.lang.String); + method public boolean isResetPasswordTokenActive(android.content.ComponentName); method public boolean isSecurityLoggingEnabled(android.content.ComponentName); method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String); method public void lockNow(); @@ -6309,6 +6313,7 @@ package android.app.admin { method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean requestBugreport(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); + method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int); method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName); method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName); @@ -6357,6 +6362,7 @@ package android.app.admin { method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long); + method public boolean setResetPasswordToken(android.content.ComponentName, byte[]); method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName); method public void setScreenCaptureDisabled(android.content.ComponentName, boolean); method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); @@ -6591,6 +6597,7 @@ package android.app.assist { method public int getTextStyle(); method public int getTop(); method public android.graphics.Matrix getTransformation(); + method public java.lang.String getUrl(); method public int getVisibility(); method public int getWidth(); method public boolean isAccessibilityFocused(); @@ -9246,7 +9253,7 @@ package android.content { field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"; field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT"; field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY"; - field public static final java.lang.String EXTRA_QUICK_VIEW_PLAIN = "android.intent.extra.QUICK_VIEW_PLAIN"; + field public static final java.lang.String EXTRA_QUICK_VIEW_ADVANCED = "android.intent.extra.QUICK_VIEW_ADVANCED"; field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE"; field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER"; field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME"; @@ -22409,7 +22416,7 @@ package android.media { method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; method public void prepareAsync() throws java.lang.IllegalStateException; - method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; + method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException; method public void release(); method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException; @@ -22424,6 +22431,7 @@ package android.media { method public void setAuxEffectSendLevel(float); method public void setBufferingParams(android.media.BufferingParams); method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; + method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; @@ -22436,6 +22444,7 @@ package android.media { method public void setNextMediaPlayer(android.media.MediaPlayer); method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener); method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener); + method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener); method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener); method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler); method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener); @@ -22500,9 +22509,8 @@ package android.media { method public abstract void onCompletion(android.media.MediaPlayer); } - public static abstract class MediaPlayer.OnDrmConfigCallback { - ctor public MediaPlayer.OnDrmConfigCallback(); - method public void onDrmConfig(android.media.MediaPlayer); + public static abstract interface MediaPlayer.OnDrmConfigListener { + method public abstract void onDrmConfig(android.media.MediaPlayer); } public static abstract interface MediaPlayer.OnDrmInfoListener { @@ -24189,6 +24197,7 @@ package android.media.tv { field public static final java.lang.String COLUMN_AUTHOR = "author"; field public static final java.lang.String COLUMN_AVAILABILITY = "availability"; field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre"; + field public static final java.lang.String COLUMN_BROWSABLE = "browsable"; field public static final java.lang.String COLUMN_DURATION_MILLIS = "duration_millis"; field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number"; @@ -24331,11 +24340,13 @@ package android.media.tv { field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE"; field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; + field public static final java.lang.String ACTION_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED"; field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"; field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS"; field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME"; + field public static final java.lang.String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID"; field public static final int INPUT_STATE_CONNECTED = 0; // 0x0 field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 @@ -44410,6 +44421,7 @@ package android.view { field public static final int AXIS_RX = 12; // 0xc field public static final int AXIS_RY = 13; // 0xd field public static final int AXIS_RZ = 14; // 0xe + field public static final int AXIS_SCROLL = 26; // 0x1a field public static final int AXIS_SIZE = 3; // 0x3 field public static final int AXIS_THROTTLE = 19; // 0x13 field public static final int AXIS_TILT = 25; // 0x19 @@ -45965,6 +45977,7 @@ package android.view { method public abstract void setTextLines(int[], int[]); method public abstract void setTextStyle(float, int, int, int); method public abstract void setTransformation(android.graphics.Matrix); + method public abstract void setUrl(java.lang.String); method public abstract void setVisibility(int); field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1 } @@ -47643,7 +47656,7 @@ package android.view.textclassifier { public final class TextClassificationManager { method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); - method public synchronized android.view.textclassifier.TextClassifier getDefaultTextClassifier(); + method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); } public final class TextClassificationResult { diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 7015381dc4fb..b336472666bd 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -53,6 +53,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IUserManager; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SELinux; @@ -1004,7 +1005,8 @@ public final class Pm { // In non-split user mode, userId can only be SYSTEM int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; info = mUm.createRestrictedProfile(name, parentUserId); - mAm.addSharedAccountsFromParentUser(parentUserId, userId); + mAm.addSharedAccountsFromParentUser(parentUserId, userId, + (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); } else if (userId < 0) { info = mUm.createUser(name, flags); } else { diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 02636819781a..7d039ef221d7 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -1802,7 +1802,7 @@ public class AccountManager { public void addSharedAccountsFromParentUser(UserHandle parentUser, UserHandle user) { try { mService.addSharedAccountsFromParentUser(parentUser.getIdentifier(), - user.getIdentifier()); + user.getIdentifier(), mContext.getOpPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index e0fdac146f68..49cd2c6a3953 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -80,7 +80,7 @@ interface IAccountManager { /* Shared accounts */ Account[] getSharedAccountsAsUser(int userId); boolean removeSharedAccountAsUser(in Account account, int userId); - void addSharedAccountsFromParentUser(int parentUserId, int userId); + void addSharedAccountsFromParentUser(int parentUserId, int userId, String opPackageName); /* Account renaming. */ void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index fb927e96d1f5..6dd31a852172 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -357,7 +357,8 @@ public class AppOpsManager { public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground"; - private static final int[] RUNTIME_PERMISSIONS_OPS = { + private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = { + // RUNTIME PERMISSIONS // Contacts OP_READ_CONTACTS, OP_WRITE_CONTACTS, @@ -392,7 +393,13 @@ public class AppOpsManager { // Camera OP_CAMERA, // Body sensors - OP_BODY_SENSORS + OP_BODY_SENSORS, + + // APPOP PERMISSIONS + OP_ACCESS_NOTIFICATIONS, + OP_SYSTEM_ALERT_WINDOW, + OP_WRITE_SETTINGS, + OP_REQUEST_INSTALL_PACKAGES, }; /** @@ -926,9 +933,9 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_RUN_IN_BACKGROUND AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME AppOpsManager.MODE_ALLOWED, - AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES + AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES AppOpsManager.MODE_ALLOWED, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE - AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND + AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND }; /** @@ -1018,7 +1025,7 @@ public class AppOpsManager { /** * Mapping from a permission to the corresponding app op. */ - private static HashMap<String, Integer> sRuntimePermToOp = new HashMap<>(); + private static HashMap<String, Integer> sPermToOp = new HashMap<>(); static { if (sOpToSwitch.length != _NUM_OP) { @@ -1058,9 +1065,9 @@ public class AppOpsManager { sOpStrToOp.put(sOpToString[i], i); } } - for (int op : RUNTIME_PERMISSIONS_OPS) { + for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) { if (sOpPerms[op] != null) { - sRuntimePermToOp.put(sOpPerms[op], op); + sPermToOp.put(sOpPerms[op], op); } } } @@ -1112,12 +1119,12 @@ public class AppOpsManager { /** * Retrieve the app op code for a permission, or null if there is not one. - * This API is intended to be used for mapping runtime permissions to the - * corresponding app op. + * This API is intended to be used for mapping runtime or appop permissions + * to the corresponding app op. * @hide */ public static int permissionToOpCode(String permission) { - Integer boxedOpCode = sRuntimePermToOp.get(permission); + Integer boxedOpCode = sPermToOp.get(permission); return boxedOpCode != null ? boxedOpCode : OP_NONE; } @@ -1462,7 +1469,7 @@ public class AppOpsManager { * @return The app op associated with the permission or null. */ public static String permissionToOp(String permission) { - final Integer opCode = sRuntimePermToOp.get(permission); + final Integer opCode = sPermToOp.get(permission); if (opCode == null) { return null; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 45a46b302178..812daf8da8a9 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3684,7 +3684,8 @@ public class Notification implements Parcelable mContext, backgroundColor); mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor( mContext, backgroundColor); - mActionBarColor = NotificationColorUtil.resolveActionBarColor(backgroundColor); + mActionBarColor = NotificationColorUtil.resolveActionBarColor(mContext, + backgroundColor); } } @@ -4166,12 +4167,21 @@ public class Notification implements Parcelable mN.extras.putCharSequence(EXTRA_SUB_TEXT, newSummary); } } + Boolean colorized = (Boolean) mN.extras.get(EXTRA_COLORIZED); + mN.extras.putBoolean(EXTRA_COLORIZED, false); + RemoteViews header = makeNotificationHeader(); + if (summary != null) { mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary); } else { mN.extras.remove(EXTRA_SUB_TEXT); } + if (colorized != null) { + mN.extras.putBoolean(EXTRA_COLORIZED, colorized); + } else { + mN.extras.remove(EXTRA_COLORIZED); + } mN.color = color; return header; } diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java index 6ee478059171..a38fd4387e90 100644 --- a/core/java/android/app/QueuedWork.java +++ b/core/java/android/app/QueuedWork.java @@ -16,86 +16,249 @@ package android.app; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.LinkedList; /** - * Internal utility class to keep track of process-global work that's - * outstanding and hasn't been finished yet. + * Internal utility class to keep track of process-global work that's outstanding and hasn't been + * finished yet. + * + * New work will be {@link #queue queued}. + * + * It is possible to add 'finisher'-runnables that are {@link #waitToFinish guaranteed to be run}. + * This is used to make sure the work has been finished. + * + * This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism + * to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for + * other things in the future. * - * This was created for writing SharedPreference edits out - * asynchronously so we'd have a mechanism to wait for the writes in - * Activity.onPause and similar places, but we may use this mechanism - * for other things in the future. + * The queued asynchronous work is performed on a separate, dedicated thread. * * @hide */ public class QueuedWork { + private static final String LOG_TAG = QueuedWork.class.getSimpleName(); + private static final boolean DEBUG = true; + + /** Delay for delayed runnables, as big as possible but low enough to be barely perceivable */ + private static final long DELAY = 100; + + /** Lock for this class */ + private static final Object sLock = new Object(); + + /** + * Used to make sure that only one thread is processing work items at a time. This means that + * they are processed in the order added. + * + * This is separate from {@link #sLock} as this is held the whole time while work is processed + * and we do not want to stall the whole class. + */ + private static Object sProcessingWork = new Object(); + + /** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */ + @GuardedBy("sLock") + private static final LinkedList<Runnable> sFinishers = new LinkedList<>(); - // The set of Runnables that will finish or wait on any async - // activities started by the application. - private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = - new ConcurrentLinkedQueue<Runnable>(); + /** {@link #getHandler() Lazily} created handler */ + @GuardedBy("sLock") + private static Handler sHandler = null; - private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class + /** Work queued via {@link #queue} */ + @GuardedBy("sLock") + private static final LinkedList<Runnable> sWork = new LinkedList<>(); + + /** If new work can be delayed or not */ + @GuardedBy("sLock") + private static boolean sCanDelay = true; /** - * Returns a single-thread Executor shared by the entire process, - * creating it if necessary. + * Lazily create a handler on a separate thread. + * + * @return the handler */ - public static ExecutorService singleThreadExecutor() { - synchronized (QueuedWork.class) { - if (sSingleThreadExecutor == null) { - // TODO: can we give this single thread a thread name? - sSingleThreadExecutor = Executors.newSingleThreadExecutor(); + private static Handler getHandler() { + synchronized (sLock) { + if (sHandler == null) { + HandlerThread handlerThread = new HandlerThread("queued-work-looper", + Process.THREAD_PRIORITY_FOREGROUND); + handlerThread.start(); + + sHandler = new QueuedWorkHandler(handlerThread.getLooper()); } - return sSingleThreadExecutor; + return sHandler; } } /** - * Add a runnable to finish (or wait for) a deferred operation - * started in this context earlier. Typically finished by e.g. - * an Activity#onPause. Used by SharedPreferences$Editor#startCommit(). + * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}. + * + * Used by SharedPreferences$Editor#startCommit(). * - * Note that this doesn't actually start it running. This is just - * a scratch set for callers doing async work to keep updated with - * what's in-flight. In the common case, caller code - * (e.g. SharedPreferences) will pretty quickly call remove() - * after an add(). The only time these Runnables are run is from - * waitToFinish(), below. + * Note that this doesn't actually start it running. This is just a scratch set for callers + * doing async work to keep updated with what's in-flight. In the common case, caller code + * (e.g. SharedPreferences) will pretty quickly call remove() after an add(). The only time + * these Runnables are run is from {@link #waitToFinish}. + * + * @param finisher The runnable to add as finisher */ - public static void add(Runnable finisher) { - sPendingWorkFinishers.add(finisher); + public static void addFinisher(Runnable finisher) { + synchronized (sLock) { + sFinishers.add(finisher); + } } - public static void remove(Runnable finisher) { - sPendingWorkFinishers.remove(finisher); + /** + * Remove a previously {@link #addFinisher added} finisher-runnable. + * + * @param finisher The runnable to remove. + */ + public static void removeFinisher(Runnable finisher) { + synchronized (sLock) { + sFinishers.remove(finisher); + } } /** - * Finishes or waits for async operations to complete. - * (e.g. SharedPreferences$Editor#startCommit writes) + * Trigger queued work to be processed immediately. The queued work is processed on a separate + * thread asynchronous. While doing that run and process all finishers on this thread. The + * finishers can be implemented in a way to check weather the queued work is finished. * - * Is called from the Activity base class's onPause(), after - * BroadcastReceiver's onReceive, after Service command handling, - * etc. (so async work is never lost) + * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive, + * after Service command handling, etc. (so async work is never lost) */ public static void waitToFinish() { - Runnable toFinish; - while ((toFinish = sPendingWorkFinishers.poll()) != null) { - toFinish.run(); + long startTime = 0; + boolean hadMessages = false; + + if (DEBUG) { + startTime = System.currentTimeMillis(); + } + + Handler handler = getHandler(); + + synchronized (sLock) { + if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) { + // Delayed work will be processed at processPendingWork() below + handler.removeMessages(QueuedWorkHandler.MSG_RUN); + + if (DEBUG) { + hadMessages = true; + Log.d(LOG_TAG, "waiting"); + } + } + + // We should not delay any work as this might delay the finishers + sCanDelay = false; + } + + processPendingWork(); + + try { + while (true) { + Runnable finisher; + + synchronized (sLock) { + finisher = sFinishers.poll(); + } + + if (finisher == null) { + break; + } + + finisher.run(); + } + } finally { + sCanDelay = true; + } + + if (DEBUG) { + long waitTime = System.currentTimeMillis() - startTime; + + if (waitTime > 0 || hadMessages) { + Log.d(LOG_TAG, "waited " + waitTime + " ms"); + } + } + } + + /** + * Queue a work-runnable for processing asynchronously. + * + * @param work The new runnable to process + * @param shouldDelay If the message should be delayed + */ + public static void queue(Runnable work, boolean shouldDelay) { + Handler handler = getHandler(); + + synchronized (sLock) { + sWork.add(work); + + if (shouldDelay && sCanDelay) { + handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); + } else { + handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); + } } } - + /** - * Returns true if there is pending work to be done. Note that the - * result is out of data as soon as you receive it, so be careful how you - * use it. + * @return True iff there is any {@link #queue async work queued}. */ public static boolean hasPendingWork() { - return !sPendingWorkFinishers.isEmpty(); + synchronized (sLock) { + return !sWork.isEmpty(); + } + } + + private static void processPendingWork() { + long startTime = 0; + + if (DEBUG) { + startTime = System.currentTimeMillis(); + } + + synchronized (sProcessingWork) { + LinkedList<Runnable> work; + + synchronized (sLock) { + work = (LinkedList<Runnable>) sWork.clone(); + sWork.clear(); + + // Remove all msg-s as all work will be processed now + getHandler().removeMessages(QueuedWorkHandler.MSG_RUN); + } + + if (work.size() > 0) { + for (Runnable w : work) { + w.run(); + } + + if (DEBUG) { + Log.d(LOG_TAG, "processing " + work.size() + " items took " + + +(System.currentTimeMillis() - startTime) + " ms"); + } + } + } + } + + private static class QueuedWorkHandler extends Handler { + static final int MSG_RUN = 1; + + QueuedWorkHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + if (msg.what == MSG_RUN) { + processPendingWork(); + } + } } - } diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index 3bb7019c212d..11ba7eec9745 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -53,7 +53,7 @@ import libcore.io.IoUtils; final class SharedPreferencesImpl implements SharedPreferences { private static final String TAG = "SharedPreferencesImpl"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private static final Object CONTENT = new Object(); // Lock ordering rules: @@ -318,6 +318,7 @@ final class SharedPreferencesImpl implements SharedPreferences { @GuardedBy("mWritingToDiskLock") volatile boolean writeToDiskResult = false; + boolean wasWritten = false; private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified, @Nullable Set<OnSharedPreferenceChangeListener> listeners, @@ -328,7 +329,8 @@ final class SharedPreferencesImpl implements SharedPreferences { this.mapToWriteToDisk = mapToWriteToDisk; } - void setDiskWriteResult(boolean result) { + void setDiskWriteResult(boolean wasWritten, boolean result) { + this.wasWritten = wasWritten; writeToDiskResult = result; writtenToDiskLatch.countDown(); } @@ -396,6 +398,8 @@ final class SharedPreferencesImpl implements SharedPreferences { } public void apply() { + final long startTime = System.currentTimeMillis(); + final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { @@ -403,15 +407,21 @@ final class SharedPreferencesImpl implements SharedPreferences { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } + + if (DEBUG && mcr.wasWritten) { + Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + + " applied after " + (System.currentTimeMillis() - startTime) + + " ms"); + } } }; - QueuedWork.add(awaitCommit); + QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); - QueuedWork.remove(awaitCommit); + QueuedWork.removeFinisher(awaitCommit); } }; @@ -503,13 +513,26 @@ final class SharedPreferencesImpl implements SharedPreferences { } public boolean commit() { + long startTime = 0; + + if (DEBUG) { + startTime = System.currentTimeMillis(); + } + MemoryCommitResult mcr = commitToMemory(); + SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; + } finally { + if (DEBUG) { + Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + + " committed after " + (System.currentTimeMillis() - startTime) + + " ms"); + } } notifyListeners(mcr); return mcr.writeToDiskResult; @@ -587,11 +610,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } } - if (DEBUG) { - Log.d(TAG, "added " + mcr.memoryStateGeneration + " -> " + mFile.getName()); - } - - QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); + QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } private static FileOutputStream createFileOutputStream(File file) { @@ -619,8 +638,31 @@ final class SharedPreferencesImpl implements SharedPreferences { // Note: must hold mWritingToDiskLock private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) { + long startTime = 0; + long existsTime = 0; + long backupExistsTime = 0; + long outputStreamCreateTime = 0; + long writeTime = 0; + long fsyncTime = 0; + long setPermTime = 0; + long fstatTime = 0; + long deleteTime = 0; + + if (DEBUG) { + startTime = System.currentTimeMillis(); + } + + boolean fileExists = mFile.exists(); + + if (DEBUG) { + existsTime = System.currentTimeMillis(); + + // Might not be set, hence init them to a default value + backupExistsTime = existsTime; + } + // Rename the current file so it may be used as a backup during the next read - if (mFile.exists()) { + if (fileExists) { boolean needsWrite = false; // Only need to write if the disk state is older than this commit @@ -639,18 +681,21 @@ final class SharedPreferencesImpl implements SharedPreferences { } if (!needsWrite) { - if (DEBUG) { - Log.d(TAG, "skipped " + mcr.memoryStateGeneration + " -> " + mFile.getName()); - } - mcr.setDiskWriteResult(true); + mcr.setDiskWriteResult(false, true); return; } - if (!mBackupFile.exists()) { + boolean backupFileExists = mBackupFile.exists(); + + if (DEBUG) { + backupExistsTime = System.currentTimeMillis(); + } + + if (!backupFileExists) { if (!mFile.renameTo(mBackupFile)) { Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); - mcr.setDiskWriteResult(false); + mcr.setDiskWriteResult(false, false); return; } } else { @@ -663,19 +708,34 @@ final class SharedPreferencesImpl implements SharedPreferences { // from the backup. try { FileOutputStream str = createFileOutputStream(mFile); + + if (DEBUG) { + outputStreamCreateTime = System.currentTimeMillis(); + } + if (str == null) { - mcr.setDiskWriteResult(false); + mcr.setDiskWriteResult(false, false); return; } XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); + + if (DEBUG) { + writeTime = System.currentTimeMillis(); + } + FileUtils.sync(str); if (DEBUG) { - Log.d(TAG, "wrote " + mcr.memoryStateGeneration + " -> " + mFile.getName()); + fsyncTime = System.currentTimeMillis(); } str.close(); ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); + + if (DEBUG) { + setPermTime = System.currentTimeMillis(); + } + try { final StructStat stat = Os.stat(mFile.getPath()); synchronized (mLock) { @@ -685,12 +745,30 @@ final class SharedPreferencesImpl implements SharedPreferences { } catch (ErrnoException e) { // Do nothing } + + if (DEBUG) { + fstatTime = System.currentTimeMillis(); + } + // Writing was successful, delete the backup file if there is one. mBackupFile.delete(); + if (DEBUG) { + deleteTime = System.currentTimeMillis(); + } + mDiskStateGeneration = mcr.memoryStateGeneration; - mcr.setDiskWriteResult(true); + mcr.setDiskWriteResult(true, true); + + Log.d(TAG, "write: " + (existsTime - startTime) + "/" + + (backupExistsTime - startTime) + "/" + + (outputStreamCreateTime - startTime) + "/" + + (writeTime - startTime) + "/" + + (fsyncTime - startTime) + "/" + + (setPermTime - startTime) + "/" + + (fstatTime - startTime) + "/" + + (deleteTime - startTime)); return; } catch (XmlPullParserException e) { @@ -698,12 +776,13 @@ final class SharedPreferencesImpl implements SharedPreferences { } catch (IOException e) { Log.w(TAG, "writeToFile: Got exception:", e); } + // Clean up an unsuccessfully written file if (mFile.exists()) { if (!mFile.delete()) { Log.e(TAG, "Couldn't clean up partially-written file " + mFile); } } - mcr.setDiskWriteResult(false); + mcr.setDiskWriteResult(false, false); } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index f1ccabe57e32..0eb47a35a47b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -28,6 +28,7 @@ import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.Activity; import android.app.IServiceConnection; +import android.app.KeyguardManager; import android.app.admin.SecurityLog.SecurityEvent; import android.content.ComponentName; import android.content.Context; @@ -2694,6 +2695,11 @@ public class DevicePolicyManager { * Force a new device unlock password (the password needed to access the entire device, not for * individual accounts) on the user. This takes effect immediately. * <p> + * <em>For device owner and profile owners targeting SDK level + * {@link android.os.Build.VERSION_CODES#O} or above, this API is no longer available and will + * throw {@link SecurityException}. Please use the new API {@link #resetPasswordWithToken} + * instead. </em> + * <p> * <em>Note: This API has been limited as of {@link android.os.Build.VERSION_CODES#N} for * device admins that are not device owner and not profile owner. * The password can now only be changed if there is currently no password set. Device owner @@ -2740,6 +2746,127 @@ public class DevicePolicyManager { } /** + * Called by a profile or device owner to provision a token which can later be used to reset the + * device lockscreen password (if called by device owner), or work challenge (if called by + * profile owner), via {@link #resetPasswordWithToken}. + * <p> + * If the user currently has a lockscreen password, the provisioned token will not be + * immediately usable; it only becomes active after the user performs a confirm credential + * operation, which can be triggered by {@link KeyguardManager#createConfirmDeviceCredentialIntent}. + * If the user has no lockscreen password, the token is activated immediately. In all cases, + * the active state of the current token can be checked by {@link #isResetPasswordTokenActive}. + * For security reasons, un-activated tokens are only stored in memory and will be lost once + * the device reboots. In this case a new token needs to be provisioned again. + * <p> + * Once provisioned and activated, the token will remain effective even if the user changes + * or clears the lockscreen password. + * <p> + * <em>This token is highly sensitive and should be treated at the same level as user + * credentials. In particular, NEVER store this token on device in plaintext, especially in + * Device-Encrypted storage if the token will be used to reset password on FBE devices before + * user unlocks. + * </em> + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param token a secure token a least 32-byte long, which must be generated by a + * cryptographically strong random number generator. + * @return true if the operation is successful, false otherwise. + * @throws IllegalArgumentException if the supplied token is invalid. + * @throws SecurityException + */ + public boolean setResetPasswordToken(ComponentName admin, byte[] token) { + throwIfParentInstance("setResetPasswordToken"); + if (mService != null) { + try { + return mService.setResetPasswordToken(admin, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by a profile or device owner to revoke the current password reset token. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return true if the operation is successful, false otherwise. + */ + public boolean clearResetPasswordToken(ComponentName admin) { + throwIfParentInstance("clearResetPasswordToken"); + if (mService != null) { + try { + return mService.clearResetPasswordToken(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by a profile or device owner to check if the current reset password token is active. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return true if the token is active, false otherwise. + * @throws IllegalStateException if no token has been set. + */ + public boolean isResetPasswordTokenActive(ComponentName admin) { + throwIfParentInstance("isResetPasswordTokenActive"); + if (mService != null) { + try { + return mService.isResetPasswordTokenActive(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device or profile owner to force set a new device unlock password or a work profile + * challenge on current user. This takes effect immediately. + * <p> + * Unlike {@link #resetPassword}, this API can change the password even before the user or + * device is unlocked or decrypted. The supplied token must have been previously provisioned via + * {@link #setResetPasswordToken}, and in active state {@link #isResetPasswordTokenActive}. + * <p> + * The given password must be sufficient for the current password quality and length constraints + * as returned by {@link #getPasswordQuality(ComponentName)} and + * {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet these constraints, then + * it will be rejected and false returned. Note that the password may be a stronger quality + * (containing alphanumeric characters when the requested quality is only numeric), in which + * case the currently active quality will be increased to match. + * <p> + * Calling with a null or empty password will clear any existing PIN, pattern or password if the + * current password constraints allow it. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param password The new password for the user. Null or empty clears the password. + * @param token the password reset token previously provisioned by #setResetPasswordToken. + * @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and + * {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}. + * @return Returns true if the password was applied, or false if it is not acceptable for the + * current constraints. + * @throws SecurityException if the calling application does not own an active administrator + * that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} + * @throws IllegalStateException if the provided token is not valid. + * @throws IllegalArgumentException if the password does not meet system requirements. + */ + public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password, + byte[] token, int flags) { + throwIfParentInstance("resetPassword"); + if (mService != null) { + try { + return mService.resetPasswordWithToken(admin, password, token, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Called by an application that is administering the device to set the maximum time for user * activity until the device will lock. This limits the length that the user can set. It takes * effect immediately. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 79fe10ec6158..c2f75c83da62 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -342,4 +342,9 @@ interface IDevicePolicyManager { long getLastSecurityLogRetrievalTime(); long getLastBugReportRequestTime(); long getLastNetworkLogRetrievalTime(); + + boolean setResetPasswordToken(in ComponentName admin, in byte[] token); + boolean clearResetPasswordToken(in ComponentName admin); + boolean isResetPasswordTokenActive(in ComponentName admin); + boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags); } diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 6591fc9671f5..8d385db263df 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -17,13 +17,13 @@ import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; import android.view.View; -import android.view.ViewStructure; import android.view.ViewRootImpl; +import android.view.ViewStructure; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.view.autofill.AutoFillId; import android.view.autofill.AutoFillType; import android.view.autofill.AutoFillValue; -import android.view.autofill.AutoFillId; import java.util.ArrayList; @@ -579,6 +579,7 @@ public class AssistStructure implements Parcelable { static final int FLAGS_HAS_EXTRAS = 0x00400000; static final int FLAGS_HAS_ID = 0x00200000; static final int FLAGS_HAS_CHILDREN = 0x00100000; + static final int FLAGS_HAS_URL = 0x00080000; static final int FLAGS_ALL_CONTROL = 0xfff00000; int mFlags; @@ -587,6 +588,7 @@ public class AssistStructure implements Parcelable { CharSequence mContentDescription; ViewNodeText mText; + String mUrl; Bundle mExtras; ViewNode[] mChildren; @@ -651,6 +653,9 @@ public class AssistStructure implements Parcelable { if ((flags&FLAGS_HAS_TEXT) != 0) { mText = new ViewNodeText(in, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0); } + if ((flags&FLAGS_HAS_URL) != 0) { + mUrl = in.readString(); + } if ((flags&FLAGS_HAS_EXTRAS) != 0) { mExtras = in.readBundle(); } @@ -704,6 +709,9 @@ public class AssistStructure implements Parcelable { flags |= FLAGS_HAS_COMPLEX_TEXT; } } + if (mUrl != null) { + flags |= FLAGS_HAS_URL; + } if (mExtras != null) { flags |= FLAGS_HAS_EXTRAS; } @@ -760,6 +768,9 @@ public class AssistStructure implements Parcelable { if ((flags&FLAGS_HAS_TEXT) != 0) { mText.writeToParcel(out, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0, writeSensitive); } + if ((flags&FLAGS_HAS_URL) != 0) { + out.writeString(mUrl); + } if ((flags&FLAGS_HAS_EXTRAS) != 0) { out.writeBundle(mExtras); } @@ -1040,6 +1051,20 @@ public class AssistStructure implements Parcelable { } /** + * Returns the URL represented by this node. + * + * <p>Typically used in 2 categories of nodes: + * + * <ol> + * <li>Root node (containing the URL of the HTML page) + * <li>Child nodes that represent hyperlinks (contains the hyperlink URL). + * </ol> + */ + public String getUrl() { + return mUrl; + } + + /** * Returns any text associated with the node that is displayed to the user, or null * if there is none. */ @@ -1486,6 +1511,11 @@ public class AssistStructure implements Parcelable { public void setSanitized(boolean sanitized) { mNode.mSanitized = sanitized; } + + @Override + public void setUrl(String url) { + mNode.mUrl = url; + } } /** @hide */ @@ -1583,6 +1613,10 @@ public class AssistStructure implements Parcelable { Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor()) + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor())); } + CharSequence url = node.getUrl(); + if (url != null) { + Log.i(TAG, prefix + " URL: " + url); + } String hint = node.getHint(); if (hint != null) { Log.i(TAG, prefix + " Hint: " + hint); diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index b379c7c57554..c0c1a4dd198b 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -29,12 +29,10 @@ import android.os.RemoteException; /** * System level service for managing companion devices * - * Usage: - * To obtain an instance call - * {@link Context#getSystemService}({@link Context#COMPANION_DEVICE_SERVICE}) - * - * Then, call {@link #associate} to initiate the flow of associating current package - * with a device selected by user + * <p>To obtain an instance call {@link Context#getSystemService}({@link + * Context#COMPANION_DEVICE_SERVICE}) Then, call {@link #associate(AssociationRequest, + * Callback, Handler)} to initiate the flow of associating current package with a + * device selected by user.</p> * * @see AssociationRequest */ @@ -47,6 +45,14 @@ public final class CompanionDeviceManager { public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; /** + * The package name of the companion device discovery component. + * + * @hide + */ + public static final String COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME = + "com.android.companiondevicemanager"; + + /** * A callback to receive once at least one suitable device is found, or the search failed * (e.g. timed out) */ @@ -81,13 +87,20 @@ public final class CompanionDeviceManager { /** * Associate this app with a companion device, selected by user * - * Once at least one appropriate device is found, {@code callback} will be called with a + * <p>Once at least one appropriate device is found, {@code callback} will be called with a * {@link PendingIntent} that can be used to show the list of available devices for the user * to select. * It should be started for result (i.e. using * {@link android.app.Activity#startIntentSenderForResult}), as the resulting * {@link android.content.Intent} will contain extra {@link #EXTRA_DEVICE}, with the selected - * device. (e.g. {@link android.bluetooth.BluetoothDevice}) + * device. (e.g. {@link android.bluetooth.BluetoothDevice})</p> + * + * <p>If your app needs to be excluded from battery optimizations (run in the background) + * or to have unrestricted data access (use data in the background) you can declare that + * you use the {@link android.Manifest.permission#RUN_IN_BACKGROUND} and {@link + * android.Manifest.permission#USE_DATA_IN_BACKGROUND} respectively. Note that these + * special capabilities have a negative effect on the device's battery and user's data + * usage, therefore you should requested them when absolutely necessary.</p> * * @param request specific details about this request * @param callback will be called once there's at least one device found for user to choose from @@ -106,17 +119,16 @@ public final class CompanionDeviceManager { try { mService.associate( request, - new IOnAssociateCallback.Stub() { - + new IFindDeviceCallback.Stub() { @Override - public void onSuccess(PendingIntent launcher) throws RemoteException { + public void onSuccess(PendingIntent launcher) { finalHandler.post(() -> { callback.onDeviceFound(launcher.getIntentSender()); }); } @Override - public void onFailure(CharSequence reason) throws RemoteException { + public void onFailure(CharSequence reason) { finalHandler.post(() -> callback.onFailure(reason)); } }, diff --git a/core/java/android/companion/ICompanionDeviceManagerService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl index ff2a7eb957f0..4d779639888e 100644 --- a/core/java/android/companion/ICompanionDeviceManagerService.aidl +++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl @@ -17,13 +17,14 @@ package android.companion; import android.companion.AssociationRequest; -import android.companion.IOnAssociateCallback; - +import android.companion.ICompanionDeviceDiscoveryServiceCallback; +import android.companion.IFindDeviceCallback; /** @hide */ -interface ICompanionDeviceManagerService { +interface ICompanionDeviceDiscoveryService { void startDiscovery( in AssociationRequest request, - in IOnAssociateCallback callback, - in String callingPackage); + in String callingPackage, + in IFindDeviceCallback findCallback, + in ICompanionDeviceDiscoveryServiceCallback serviceCallback); } diff --git a/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl index c9dd345341b2..7af708e94d97 100644 --- a/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl +++ b/core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl @@ -16,9 +16,7 @@ package android.companion; -import android.bluetooth.BluetoothDevice; - /** @hide */ -interface ICompanionDeviceManagerServiceCallback { - void onDeviceSelected(in BluetoothDevice device); +interface ICompanionDeviceDiscoveryServiceCallback { + void onDeviceSelected(String packageName, int userId); } diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 065e31be9adf..1d30ada25c62 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -16,7 +16,7 @@ package android.companion; -import android.companion.IOnAssociateCallback; +import android.companion.IFindDeviceCallback; import android.companion.AssociationRequest; /** @@ -26,8 +26,8 @@ import android.companion.AssociationRequest; */ interface ICompanionDeviceManager { void associate(in AssociationRequest request, - in IOnAssociateCallback callback, - in String callingPackage); //TODO int userId? + in IFindDeviceCallback callback, + in String callingPackage); //TODO add these // boolean haveNotificationAccess(String packageName); diff --git a/core/java/android/companion/IOnAssociateCallback.aidl b/core/java/android/companion/IFindDeviceCallback.aidl index 4867eadd2577..919e15198efa 100644 --- a/core/java/android/companion/IOnAssociateCallback.aidl +++ b/core/java/android/companion/IFindDeviceCallback.aidl @@ -19,7 +19,7 @@ package android.companion; import android.app.PendingIntent; /** @hide */ -interface IOnAssociateCallback { +interface IFindDeviceCallback { void onSuccess(in PendingIntent launcher); void onFailure(in CharSequence reason); } diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 485d0784b766..c3d6606089cb 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -211,16 +211,16 @@ public abstract class BroadcastReceiver { // of the list to finish the broadcast, so we don't block this // thread (which may be the main thread) to have it finished. // - // Note that we don't need to use QueuedWork.add() with the + // Note that we don't need to use QueuedWork.addFinisher() with the // runnable, since we know the AM is waiting for us until the // executor gets to it. - QueuedWork.singleThreadExecutor().execute( new Runnable() { + QueuedWork.queue(new Runnable() { @Override public void run() { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast after work to component " + mToken); sendFinished(mgr); } - }); + }, false); } else { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast to component " + mToken); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 028a7bcf9539..b0505ac4a3a2 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -669,13 +669,14 @@ public class Intent implements Parcelable, Cloneable { * preview. {@link #getClipData} contains an optional list of content URIs * if there is more than one item to preview. {@link #EXTRA_INDEX} is an * optional index of the URI in the clip data to show first. - * If {@link #EXTRA_QUICK_VIEW_PLAIN} is true, then the quick viewer should show - * basic UI without any extra features other than quick viewing the passed items. - * Especially, the quick viewer should not let users open the passed files - * in other apps, which includes sharing, opening, editing, printing, etc in the - * plain mode. + * <p>By default quick viewers are supposed to be lightweight and focus on + * previewing the content only. They should not expose features such as printing, + * opening in an external app, deleting, rotating, casting, etc. + * However, if {@link #EXTRA_QUICK_VIEW_ADVANCED} is true, then the quick viewer + * may show advanced UI which includes convenience actions suitable for the passed + * Uris. * <p>Output: nothing. - * @see #EXTRA_QUICK_VIEW_HIDE_DEFAULT_ACTIONS + * @see #EXTRA_QUICK_VIEW_ADVANCED * @see #EXTRA_INDEX */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @@ -4413,19 +4414,15 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INDEX = "android.intent.extra.INDEX"; /** - * Shows a plain quick viewer UI which doesn't provide any extra features other than - * quick viewing the items. - * - * <p>Especially, the quick viewer should not let users open the quick viewed files - * in other apps, which includes sharing, opening, editing, printing, etc. - * - * <p>This feature is optional, and may not be handled by all quick viewers. + * Tells the quick viewer to show additional UI actions suitable for the passed Uris, + * such as opening in other apps, sharing, opening, editing, printing, deleting, + * casting, etc. * * <p>The value is boolean. By default false. * @see ACTION_QUICK_VIEW */ - public static final String EXTRA_QUICK_VIEW_PLAIN = - "android.intent.extra.QUICK_VIEW_PLAIN"; + public static final String EXTRA_QUICK_VIEW_ADVANCED = + "android.intent.extra.QUICK_VIEW_ADVANCED"; /** * Optional boolean extra indicating whether quiet mode has been switched on or off. diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index c8f64061e6fc..40deeae28335 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -37,6 +37,7 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.MaskableIconDrawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -803,7 +804,15 @@ public class LauncherApps { } try { final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); - return (bmp == null) ? null : new BitmapDrawable(mContext.getResources(), bmp); + if (bmp != null) { + BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp); + if (shortcut.hasMaskableBitmap()) { + return new MaskableIconDrawable(null, dr); + } else { + return dr; + } + } + return null; } finally { try { pfd.close(); @@ -821,7 +830,8 @@ public class LauncherApps { return loadDrawableResourceFromPackage(shortcut.getPackage(), icon.getResId(), shortcut.getUserHandle(), density); } - case Icon.TYPE_BITMAP: { + case Icon.TYPE_BITMAP: + case Icon.TYPE_BITMAP_MASKABLE: { return icon.loadDrawable(mContext); } default: diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index b4dcdf7e4e51..f1f268306bb2 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -92,6 +92,9 @@ public final class ShortcutInfo implements Parcelable { public static final int FLAG_IMMUTABLE = 1 << 8; /** @hide */ + public static final int FLAG_MASKABLE_BITMAP = 1 << 9; + + /** @hide */ @IntDef(flag = true, value = { FLAG_DYNAMIC, @@ -103,6 +106,7 @@ public final class ShortcutInfo implements Parcelable { FLAG_DISABLED, FLAG_STRINGS_RESOLVED, FLAG_IMMUTABLE, + FLAG_MASKABLE_BITMAP, }) @Retention(RetentionPolicy.SOURCE) public @interface ShortcutFlags {} @@ -690,6 +694,7 @@ public final class ShortcutInfo implements Parcelable { switch (icon.getType()) { case Icon.TYPE_RESOURCE: case Icon.TYPE_BITMAP: + case Icon.TYPE_BITMAP_MASKABLE: break; // OK default: throw getInvalidIconException(); @@ -815,8 +820,9 @@ public final class ShortcutInfo implements Parcelable { * <p>Tints set with {@link Icon#setTint} or {@link Icon#setTintList} are not supported * and will be ignored. * - * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)} and - * {@link Icon#createWithResource} are supported. + * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)}, + * {@link Icon#createWithMaskableBitmap(Bitmap)} + * and {@link Icon#createWithResource} are supported. * Other types, such as URI-based icons, are not supported. * * @see LauncherApps#getShortcutIconDrawable(ShortcutInfo, int) @@ -1442,6 +1448,15 @@ public final class ShortcutInfo implements Parcelable { } /** + * Return whether a shortcut's icon is maskable. + * + * @hide internal/unit tests only + */ + public boolean hasMaskableBitmap() { + return hasFlags(FLAG_MASKABLE_BITMAP); + } + + /** * Return whether a shortcut only contains "key" information only or not. If true, only the * following fields are available. * <ul> diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index acb1d07c36a4..719a9577f80e 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2998,8 +2998,8 @@ public class ConnectivityManager { * * This function behaves identically to the non-timedout version, but if a suitable * network is not found within the given time (in milliseconds) the - * {@link NetworkCallback#unavailable} callback is called. The request must - * still be released normally by calling {@link unregisterNetworkCallback(NetworkCallback)}. + * {@link NetworkCallback#onUnavailable()} callback is called. The request must + * still be released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)}. * * <p>This method requires the caller to hold either the * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission @@ -3011,10 +3011,7 @@ public class ConnectivityManager { * the callbacks must not be shared - they uniquely specify * this request. * @param timeoutMs The time in milliseconds to attempt looking for a suitable network - * before {@link NetworkCallback#unavailable} is called. - * - * TODO: Make timeouts work and then unhide this method. - * + * before {@link NetworkCallback#onUnavailable()} is called. * @hide */ public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback, @@ -3024,13 +3021,6 @@ public class ConnectivityManager { } /** - * The maximum number of milliseconds the framework will look for a suitable network - * during a timeout-equiped call to {@link requestNetwork}. - * {@hide} - */ - public final static int MAX_NETWORK_REQUEST_TIMEOUT_MS = 100 * 60 * 1000; - - /** * The lookup key for a {@link Network} object included with the intent after * successfully finding a network for the applications request. Retrieve it with * {@link android.content.Intent#getParcelableExtra(String)}. diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java index e5f0bf000f64..31a74dc77250 100644 --- a/core/java/android/net/NetworkKey.java +++ b/core/java/android/net/NetworkKey.java @@ -24,6 +24,7 @@ import android.net.wifi.WifiSsid; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Log; import java.util.Objects; @@ -41,6 +42,8 @@ import java.util.Objects; // etc.) so that clients can pull out these details depending on the type of network. public class NetworkKey implements Parcelable { + private static final String TAG = "NetworkKey"; + /** A wifi network, for which {@link #wifiKey} will be populated. */ public static final int TYPE_WIFI = 1; @@ -59,13 +62,28 @@ public class NetworkKey implements Parcelable { /** * Constructs a new NetworkKey for the given wifi {@link ScanResult}. * - * @throws IllegalArgumentException if the given ScanResult is malformed + * @return A new {@link NetworkKey} instance or <code>null</code> if the given + * {@link ScanResult} instance is malformed. * @hide */ - public static NetworkKey createFromScanResult(ScanResult result) { - return new NetworkKey( - new WifiKey( - '"' + result.wifiSsid.toString() + '"', result.BSSID)); + @Nullable + public static NetworkKey createFromScanResult(@Nullable ScanResult result) { + if (result != null && result.wifiSsid != null) { + final String ssid = result.wifiSsid.toString(); + final String bssid = result.BSSID; + if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE) + && !TextUtils.isEmpty(bssid)) { + WifiKey wifiKey; + try { + wifiKey = new WifiKey(String.format("\"%s\"", ssid), bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Unable to create WifiKey.", e); + return null; + } + return new NetworkKey(wifiKey); + } + } + return null; } /** @@ -83,7 +101,14 @@ public class NetworkKey implements Parcelable { final String bssid = wifiInfo.getBSSID(); if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE) && !TextUtils.isEmpty(bssid)) { - return new NetworkKey(new WifiKey(ssid, bssid)); + WifiKey wifiKey; + try { + wifiKey = new WifiKey(ssid, bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Unable to create WifiKey.", e); + return null; + } + return new NetworkKey(wifiKey); } } return null; diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 11b960643e5a..ee8eed1906f4 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -348,8 +348,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (msg.what == UPDATE_SLIDER) { if (mSeekBar != null) { mLastProgress = msg.arg1; - mLastAudibleStreamVolume = Math.abs(msg.arg2); - final boolean muted = msg.arg2 < 0; + mLastAudibleStreamVolume = msg.arg2; + final boolean muted = ((Boolean)msg.obj).booleanValue(); if (muted != mMuted) { mMuted = muted; if (mCallback != null) { @@ -362,8 +362,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) { - final int arg2 = lastAudibleVolume * (mute ? -1 : 1); - obtainMessage(UPDATE_SLIDER, volume, arg2).sendToTarget(); + obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget(); } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2dfba28ad6e3..9ecc63896e2f 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6628,6 +6628,14 @@ public final class Settings { "lock_screen_show_notifications"; /** + * This preference stores the last stack active task time for each user, which affects what + * tasks will be visible in Overview. + * @hide + */ + public static final String OVERVIEW_LAST_STACK_ACTIVE_TIME = + "overview_last_stack_active_time"; + + /** * List of TV inputs that are currently hidden. This is a string * containing the IDs of all hidden TV inputs. Each ID is encoded by * {@link android.net.Uri#encode(String)} and separated by ':'. diff --git a/core/java/android/text/Emoji.java b/core/java/android/text/Emoji.java index b6d720d453e9..83810b03b463 100644 --- a/core/java/android/text/Emoji.java +++ b/core/java/android/text/Emoji.java @@ -164,6 +164,8 @@ public class Emoji { public static int VARIATION_SELECTOR_16 = 0xFE0F; + public static int CANCEL_TAG = 0xE007F; + // Returns true if the given code point is regional indicator symbol. public static boolean isRegionalIndicatorSymbol(int codepoint) { return 0x1F1E6 <= codepoint && codepoint <= 0x1F1FF; @@ -188,4 +190,13 @@ public class Emoji { public static boolean isKeycapBase(int codePoint) { return ('0' <= codePoint && codePoint <= '9') || codePoint == '#' || codePoint == '*'; } + + /** + * Returns true if the character can be a part of tag_spec in emoji tag sequence. + * + * Note that 0xE007F (CANCEL TAG) is not included. + */ + public static boolean isTagSpecChar(int codePoint) { + return 0xE0020 <= codePoint && codePoint <= 0xE007E; + } } diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java index 90559dcdbcf4..5f0a46d815ea 100644 --- a/core/java/android/text/method/BaseKeyListener.java +++ b/core/java/android/text/method/BaseKeyListener.java @@ -145,8 +145,11 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener // The number of following RIS code points is even. final int STATE_EVEN_NUMBERED_RIS = 11; + // The offset is in emoji tag sequence. + final int STATE_IN_TAG_SEQUENCE = 12; + // The state machine has been stopped. - final int STATE_FINISHED = 12; + final int STATE_FINISHED = 13; int deleteCharCount = 0; // Char count to be deleted by backspace. int lastSeenVSCharCount = 0; // Char count of previous variation selector. @@ -173,6 +176,8 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener state = STATE_BEFORE_KEYCAP; } else if (Emoji.isEmoji(codePoint)) { state = STATE_BEFORE_EMOJI; + } else if (codePoint == Emoji.CANCEL_TAG) { + state = STATE_IN_TAG_SEQUENCE; } else { state = STATE_FINISHED; } @@ -275,6 +280,20 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener state = STATE_FINISHED; } break; + case STATE_IN_TAG_SEQUENCE: + if (Emoji.isTagSpecChar(codePoint)) { + deleteCharCount += 2; /* Char count of emoji tag spec character. */ + // Keep the same state. + } else if (Emoji.isEmoji(codePoint)) { + deleteCharCount += Character.charCount(codePoint); + state = STATE_FINISHED; + } else { + // Couldn't find tag_base character. Delete the last tag_term character. + deleteCharCount = 2; // for U+E007F + state = STATE_FINISHED; + } + // TODO: Need handle emoji variation selectors. Issue 35224297 + break; default: throw new IllegalArgumentException("state " + state + " is unknown"); } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 2129039b1b8c..5f55bdc0e0d3 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1008,7 +1008,6 @@ public final class MotionEvent extends InputEvent implements Parcelable { * </p> * * @see #getAxisValue(int, int) - * {@hide} */ public static final int AXIS_SCROLL = 26; diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index cc19539b0e51..9ce23e631451 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -25,7 +25,8 @@ import android.view.autofill.AutoFillValue; /** * Container for storing additional per-view data generated by {@link View#onProvideStructure - * View.onProvideStructure}. + * View.onProvideStructure} and {@link View#onProvideAutoFillStructure + * View.onProvideAutoFillStructure}. */ public abstract class ViewStructure { @@ -33,7 +34,9 @@ public abstract class ViewStructure { * Flag used when adding virtual views for auto-fill, it indicates the contents of the view * (such as * {@link android.app.assist.AssistStructure.ViewNode#getText()} and * {@link android.app.assist.AssistStructure.ViewNode#getAutoFillValue()}) - * can be passed to the {@link android.service.autofill.AutoFillService}. + * can be passed to the {@link + * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure, + * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} call. */ public static final int AUTO_FILL_FLAG_SANITIZED = 0x1; @@ -275,7 +278,7 @@ public abstract class ViewStructure { * * @param index child index * @param virtualId id identifying the virtual child inside the custom view. - * @param flags currently {@code 0}. + * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}. */ // TODO(b/33197203, b/33802548): add CTS/unit test public abstract ViewStructure newChild(int index, int virtualId, int flags); @@ -296,7 +299,7 @@ public abstract class ViewStructure { * * @param index child index * @param virtualId id identifying the virtual child inside the custom view. - * @param flags currently {@code 0}. + * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}. */ // TODO(b/33197203, b/33802548): add CTS/unit test public abstract ViewStructure asyncNewChild(int index, int virtualId, int flags); @@ -335,4 +338,17 @@ public abstract class ViewStructure { /** @hide */ public abstract AutoFillId getAutoFillId(); + + /** + * Sets the URL represented by this node. + * + * <p>Typically used in the following situations: + * + * <ol> + * <li>In a {@link android.app.assist.AssistStructure.WindowNode#getRootViewNode()}, to set up + * the main URL of an HTML page. + * <li>On child nodes represening hyperlinks. + * </ol> + */ + public abstract void setUrl(String url); } diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java index b4890b70b706..44b21b44e93b 100644 --- a/core/java/com/android/internal/util/NotificationColorUtil.java +++ b/core/java/com/android/internal/util/NotificationColorUtil.java @@ -456,7 +456,10 @@ public class NotificationColorUtil { } } - public static int resolveActionBarColor(int backgroundColor) { + public static int resolveActionBarColor(Context context, int backgroundColor) { + if (backgroundColor == Notification.COLOR_DEFAULT) { + return context.getColor(com.android.internal.R.color.notification_action_list); + } boolean useDark = shouldUseDark(backgroundColor); final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); ColorUtilsFromCompat.colorToLAB(backgroundColor, result); diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index b380b1340232..b8c062e17645 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -45,4 +45,10 @@ interface ILockSettings { void systemReady(); void userPresent(int userId); int getStrongAuthForUser(int userId); + + long addEscrowToken(in byte[] token, int userId); + boolean removeEscrowToken(long handle, int userId); + boolean isEscrowTokenActive(long handle, int userId); + boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, in byte[] token, int userId); + void unlockUserWithToken(long tokenHandle, in byte[] token, int userId); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index ef6e6c2bda10..0aba9c28d75f 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -147,6 +147,10 @@ public class LockPatternUtils { public static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_"; public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_"; + public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_"; + + public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle"; + public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp"; private final Context mContext; private final ContentResolver mContentResolver; @@ -769,7 +773,7 @@ public class LockPatternUtils { getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword, userHandle); - addEncryptionPassword(password, computedQuality, userHandle); + updateEncryptionPasswordIfNeeded(password, computedQuality, userHandle); updatePasswordHistory(password, userHandle); } catch (RemoteException re) { // Cant do much @@ -777,7 +781,11 @@ public class LockPatternUtils { } } - private void addEncryptionPassword(String password, int quality, int userHandle) { + /** + * Update device encryption password if calling user is USER_SYSTEM and device supports + * encryption. + */ + private void updateEncryptionPasswordIfNeeded(String password, int quality, int userHandle) { // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM && LockPatternUtils.isDeviceEncryptionEnabled()) { @@ -1398,6 +1406,104 @@ public class LockPatternUtils { } /** + * Create an escrow token for the current user, which can later be used to unlock FBE + * or change user password. + * + * After adding, if the user currently has lockscreen password, he will need to perform a + * confirm credential operation in order to activate the token for future use. If the user + * has no secure lockscreen, then the token is activated immediately. + * + * @return a unique 64-bit token handle which is needed to refer to this token later. + */ + public long addEscrowToken(byte[] token, int userId) { + try { + return getLockSettings().addEscrowToken(token, userId); + } catch (RemoteException re) { + return 0L; + } + } + + /** + * Remove an escrow token. + * @return true if the given handle refers to a valid token previously returned from + * {@link #addEscrowToken}, whether it's active or not. return false otherwise. + */ + public boolean removeEscrowToken(long handle, int userId) { + try { + return getLockSettings().removeEscrowToken(handle, userId); + } catch (RemoteException re) { + return false; + } + } + + /** + * Check if the given escrow token is active or not. Only active token can be used to call + * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken} + */ + public boolean isEscrowTokenActive(long handle, int userId) { + try { + return getLockSettings().isEscrowTokenActive(handle, userId); + } catch (RemoteException re) { + return false; + } + } + + public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, + byte[] token, int userId) { + try { + if (type != CREDENTIAL_TYPE_NONE) { + if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { + throw new IllegalArgumentException("password must not be null and at least " + + "of length " + MIN_LOCK_PASSWORD_SIZE); + } + + final int computedQuality = PasswordMetrics.computeForPassword(credential).quality; + if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle, + token, userId)) { + return false; + } + setLong(PASSWORD_TYPE_KEY, Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, + computedQuality), userId); + + updateEncryptionPasswordIfNeeded(credential, computedQuality, userId); + updatePasswordHistory(credential, userId); + } else { + if (!TextUtils.isEmpty(credential)) { + throw new IllegalArgumentException("password must be emtpy for NONE type"); + } + if (!getLockSettings().setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, + tokenHandle, token, userId)) { + return false; + } + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); + + if (userId == UserHandle.USER_SYSTEM) { + // Set the encryption password to default. + updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); + setCredentialRequiredToDecrypt(false); + } + } + onAfterChangingPassword(userId); + return true; + } catch (RemoteException re) { + Log.e(TAG, "Unable to save lock password ", re); + re.rethrowFromSystemServer(); + } + return false; + } + + public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) { + try { + getLockSettings().unlockUserWithToken(tokenHandle, token, userId); + } catch (RemoteException re) { + Log.e(TAG, "Unable to unlock user with token", re); + re.rethrowFromSystemServer(); + } + } + + + /** * Callback to be notified about progress when checking credentials. */ public interface CheckCredentialProgressCallback { @@ -1559,6 +1665,14 @@ public class LockPatternUtils { break; } } - }; + } + } + + public void enableSyntheticPassword() { + setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1L, UserHandle.USER_SYSTEM); + } + + public boolean isSyntheticPasswordEnabled() { + return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0; } } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index fcb4c7b48125..3d012bf24f7a 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -109,6 +109,10 @@ public class SystemConfig { // background while in data-usage save mode, as read from the configuration files. final ArraySet<String> mAllowInDataUsageSave = new ArraySet<>(); + // These are the packages that are white-listed to be able to run background location + // without throttling, as read from the configuration files. + final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>(); + // These are the action strings of broadcasts which are whitelisted to // be delivered anonymously even to apps which target O+. final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>(); @@ -182,6 +186,10 @@ public class SystemConfig { return mAllowInDataUsageSave; } + public ArraySet<String> getAllowUnthrottledLocation() { + return mAllowUnthrottledLocation; + } + public ArraySet<String> getLinkedApps() { return mLinkedApps; } @@ -446,6 +454,17 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; + } else if ("allow-unthrottled-location".equals(name) && allowAll) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<allow-unthrottled-location> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowUnthrottledLocation.add(pkgname); + } + XmlUtils.skipCurrentTag(parser); + continue; + } else if ("allow-implicit-broadcast".equals(name) && allowAll) { String action = parser.getAttributeValue(null, "action"); if (action == null) { diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index bc257e04440d..819460e08957 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -23,6 +23,8 @@ option java_outer_classname = "NotificationServiceProto"; message NotificationServiceDumpProto { repeated NotificationRecordProto records = 1; + + ZenModeProto zen = 2; } message NotificationRecordProto { @@ -42,4 +44,21 @@ enum State { ENQUEUED = 0; POSTED = 1; + + SNOOZED = 2; +} + +message ZenModeProto { + ZenMode zen_mode = 1; + repeated string enabled_active_conditions = 2; + int32 suppressed_effects = 3; + repeated string suppressors = 4; + string policy = 5; +} + +enum ZenMode { + ZEN_MODE_OFF = 0; + ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + ZEN_MODE_NO_INTERRUPTIONS = 2; + ZEN_MODE_ALARMS = 3; }
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index cd02fbbedb7e..97fbfa5a55b6 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1813,6 +1813,22 @@ android:description="@string/permdesc_systemAlertWindow" android:protectionLevel="signature|preinstalled|appop|pre23|development" /> + <!-- Allows an app to run in the background. + <p>Protection level: signature + --> + <permission android:name="android.permission.RUN_IN_BACKGROUND" + android:label="@string/permlab_runInBackground" + android:description="@string/permdesc_runInBackground" + android:protectionLevel="signature" /> + + <!-- Allows an app to use data in the background. + <p>Protection level: signature + --> + <permission android:name="android.permission.USE_DATA_IN_BACKGROUND" + android:label="@string/permlab_useDataInBackground" + android:description="@string/permdesc_useDataInBackground" + android:protectionLevel="signature" /> + <!-- ================================== --> <!-- Permissions affecting the system wallpaper --> <!-- ================================== --> @@ -2996,7 +3012,7 @@ any metadata and intents attached. @hide --> <permission android:name="android.permission.ACCESS_NOTIFICATIONS" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|appop" /> <!-- Marker permission for applications that wish to access notification policy. <p>Protection level: normal diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 72a2e43bc8c8..fcb0e08141e4 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1286,6 +1286,16 @@ <item>com.android.networkrecommendation</item> </string-array> + <!-- The package name of the default network recommendation app. + A network recommendation provider must: + * Be granted the SCORE_NETWORKS permission. + * Include a Service for the android.net.scoring.RECOMMEND_NETWORKS action + protected by the BIND_NETWORK_RECOMMENDATION_SERVICE permission. + + This must be set to a valid network recommendation app. + --> + <string name="config_defaultNetworkRecommendationProviderPackage" translatable="false">com.android.networkrecommendation</string> + <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be replaced by an app at run-time. When disabled, only the config_hardwareFlpPackageName package will be searched for Hardware Flp, diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 23098668c082..8faa76cda808 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -816,6 +816,16 @@ <string name="permdesc_systemAlertWindow">This app can appear on top of other apps or other parts of the screen. This may interfere with normal app usage and change the way that other apps appear.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_runInBackground">run in the background</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_runInBackground">This app can run in the background. This may drain battery faster.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_useDataInBackground">use data in the background</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_useDataInBackground">This app can use data in the background. This may increase data usage.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_persistentActivity">make app always run</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_persistentActivity" product="tablet">Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the tablet.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1324e383618d..0d63a1e48420 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2790,6 +2790,7 @@ <!-- Network Recommendation --> <java-symbol type="array" name="config_networkRecommendationPackageNames" /> + <java-symbol type="string" name="config_defaultNetworkRecommendationProviderPackage" /> <!-- Whether allow 3rd party apps on internal storage. --> <java-symbol type="bool" name="config_allow3rdPartyAppOnInternal" /> diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java index 1afe9da2bfe6..fff23a04db9f 100644 --- a/core/tests/coretests/src/android/net/NetworkKeyTest.java +++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; +import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiSsid; import android.support.test.runner.AndroidJUnit4; @@ -17,7 +18,9 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class NetworkKeyTest { private static final String VALID_SSID = "\"ssid1\""; + private static final String VALID_UNQUOTED_SSID = "ssid1"; private static final String VALID_BSSID = "00:00:00:00:00:00"; + private static final String INVALID_BSSID = "invalid_bssid"; @Mock private WifiInfo mWifiInfo; @Before @@ -64,6 +67,13 @@ public class NetworkKeyTest { } @Test + public void createFromWifi_invalidBssid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); + when(mWifiInfo.getBSSID()).thenReturn(INVALID_BSSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test public void createFromWifi_validWifiInfo() throws Exception { when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); @@ -72,4 +82,72 @@ public class NetworkKeyTest { final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo); assertEquals(expected, actual); } + + @Test + public void createFromScanResult_nullInput() { + assertNull(NetworkKey.createFromScanResult(null)); + } + + @Test + public void createFromScanResult_nullWifiSsid() { + ScanResult scanResult = new ScanResult(); + scanResult.BSSID = VALID_BSSID; + + assertNull(NetworkKey.createFromScanResult(scanResult)); + } + + @Test + public void createFromScanResult_emptyWifiSsid() { + ScanResult scanResult = new ScanResult(); + scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(""); + scanResult.BSSID = VALID_BSSID; + + assertNull(NetworkKey.createFromScanResult(scanResult)); + } + + @Test + public void createFromScanResult_noneWifiSsid() { + ScanResult scanResult = new ScanResult(); + scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(WifiSsid.NONE); + scanResult.BSSID = VALID_BSSID; + + assertNull(NetworkKey.createFromScanResult(scanResult)); + } + + @Test + public void createFromScanResult_nullBssid() { + ScanResult scanResult = new ScanResult(); + scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID); + + assertNull(NetworkKey.createFromScanResult(scanResult)); + } + + @Test + public void createFromScanResult_emptyBssid() { + ScanResult scanResult = new ScanResult(); + scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID); + scanResult.BSSID = ""; + + assertNull(NetworkKey.createFromScanResult(scanResult)); + } + + @Test + public void createFromScanResult_invalidBssid() { + ScanResult scanResult = new ScanResult(); + scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID); + scanResult.BSSID = INVALID_BSSID; + + assertNull(NetworkKey.createFromScanResult(scanResult)); + } + + @Test + public void createFromScanResult_validWifiSsid() { + ScanResult scanResult = new ScanResult(); + scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID); + scanResult.BSSID = VALID_BSSID; + + NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID)); + NetworkKey actual = NetworkKey.createFromScanResult(scanResult); + assertEquals(expected, actual); + } } diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java index a260e947958e..864b48a79bb0 100644 --- a/core/tests/coretests/src/android/text/method/BackspaceTest.java +++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java @@ -37,23 +37,12 @@ public class BackspaceTest extends KeyListenerTestCase { // Sync the state to the TextView and call onKeyDown with KEYCODE_DEL key event. // Then update the state to the result of TextView. private void backspace(final EditorState state, int modifiers) { - mActivity.runOnUiThread(new Runnable() { - public void run() { - mTextView.setText(state.mText, BufferType.EDITABLE); - mTextView.setKeyListener(mKeyListener); - mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); - } - }); - mInstrumentation.waitForIdleSync(); - assertTrue(mTextView.hasWindowFocus()); + mTextView.setText(state.mText, BufferType.EDITABLE); + mTextView.setKeyListener(mKeyListener); + mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_DEL, modifiers); - mActivity.runOnUiThread(new Runnable() { - public void run() { - mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); - } - }); - mInstrumentation.waitForIdleSync(); + mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); state.mText = mTextView.getText(); state.mSelectionStart = mTextView.getSelectionStart(); @@ -247,6 +236,51 @@ public class BackspaceTest extends KeyListenerTestCase { state.assertEquals("U+1F1FA U+1F1F8 |"); backspace(state, 0); state.assertEquals("|"); + + // Incomplete sequence. (no tag_term: U+E007E) + state.setByString("'a' U+1F3F4 U+E0067 'b' |"); + backspace(state, 0); + state.assertEquals("'a' U+1F3F4 U+E0067 |"); + backspace(state, 0); + state.assertEquals("'a' U+1F3F4 |"); + backspace(state, 0); + state.assertEquals("'a' |"); + + // No tag_base + state.setByString("'a' U+E0067 U+E007F 'b' |"); + backspace(state, 0); + state.assertEquals("'a' U+E0067 U+E007F |"); + backspace(state, 0); + state.assertEquals("'a' U+E0067 |"); + backspace(state, 0); + state.assertEquals("'a' |"); + + // Isolated tag chars + state.setByString("'a' U+E0067 U+E0067 'b' |"); + backspace(state, 0); + state.assertEquals("'a' U+E0067 U+E0067 |"); + backspace(state, 0); + state.assertEquals("'a' U+E0067 |"); + backspace(state, 0); + state.assertEquals("'a' |"); + + // Isolated tab term. + state.setByString("'a' U+E007F U+E007F 'b' |"); + backspace(state, 0); + state.assertEquals("'a' U+E007F U+E007F |"); + backspace(state, 0); + state.assertEquals("'a' U+E007F |"); + backspace(state, 0); + state.assertEquals("'a' |"); + + // Immediate tag_term after tag_base + state.setByString("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b' |"); + backspace(state, 0); + state.assertEquals("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F |"); + backspace(state, 0); + state.assertEquals("'a' U+1F3F4 U+E007F |"); + backspace(state, 0); + state.assertEquals("'a' |"); } @SmallTest diff --git a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java index 1990fd021203..839d380f79b9 100644 --- a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java +++ b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java @@ -37,23 +37,12 @@ public class ForwardDeleteTest extends KeyListenerTestCase { // Sync the state to the TextView and call onKeyDown with KEYCODE_FORWARD_DEL key event. // Then update the state to the result of TextView. private void forwardDelete(final EditorState state, int modifiers) { - mActivity.runOnUiThread(new Runnable() { - public void run() { - mTextView.setText(state.mText, BufferType.EDITABLE); - mTextView.setKeyListener(mKeyListener); - mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); - } - }); - mInstrumentation.waitForIdleSync(); - assertTrue(mTextView.hasWindowFocus()); + mTextView.setText(state.mText, BufferType.EDITABLE); + mTextView.setKeyListener(mKeyListener); + mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_FORWARD_DEL, modifiers); - mActivity.runOnUiThread(new Runnable() { - public void run() { - mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); - } - }); - mInstrumentation.waitForIdleSync(); + mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); state.mText = mTextView.getText(); state.mSelectionStart = mTextView.getSelectionStart(); @@ -186,6 +175,46 @@ public class ForwardDeleteTest extends KeyListenerTestCase { state.assertEquals("| U+1F1FA"); forwardDelete(state, 0); state.assertEquals("|"); + + // Incomplete sequence. (no tag_term:U+E007E) + state.setByString("| 'a' U+1F3F4 U+E0067 'b'"); + forwardDelete(state, 0); + state.assertEquals("| U+1F3F4 U+E0067 'b'"); + forwardDelete(state, 0); + state.assertEquals("| 'b'"); + + // No tag_base + state.setByString("| 'a' U+E0067 U+E007F 'b'"); + forwardDelete(state, 0); + state.assertEquals("| 'b'"); + + // Isolated tag chars + state.setByString("| 'a' U+E0067 U+E0067 'b'"); + forwardDelete(state, 0); + state.assertEquals("| 'b'"); + + // Isolated tag base. + state.setByString("| 'a' U+1F3F4 U+1F3F4 'b'"); + forwardDelete(state, 0); + state.assertEquals("| U+1F3F4 U+1F3F4 'b'"); + forwardDelete(state, 0); + state.assertEquals("| U+1F3F4 'b'"); + forwardDelete(state, 0); + state.assertEquals("| 'b'"); + + // Isolated tab term. + state.setByString("| 'a' U+E007F U+E007F 'b'"); + forwardDelete(state, 0); + state.assertEquals("| 'b'"); + + // Immediate tag_term after tag_base + state.setByString("| 'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'"); + forwardDelete(state, 0); + state.assertEquals("| U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'"); + forwardDelete(state, 0); + state.assertEquals("| U+1F3F4 U+E007F 'b'"); + forwardDelete(state, 0); + state.assertEquals("| 'b'"); } @SmallTest diff --git a/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java b/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java index 4b4e7afdb636..f005d7b02a62 100644 --- a/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java +++ b/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java @@ -17,41 +17,26 @@ package android.text.method; import android.app.Instrumentation; -import android.test.ActivityInstrumentationTestCase2; -import android.text.format.DateUtils; +import android.test.InstrumentationTestCase; import android.view.KeyEvent; import android.widget.EditText; -import android.widget.TextViewActivity; import com.android.frameworks.coretests.R; -public abstract class KeyListenerTestCase extends - ActivityInstrumentationTestCase2<TextViewActivity> { +public abstract class KeyListenerTestCase extends InstrumentationTestCase { - protected TextViewActivity mActivity; protected Instrumentation mInstrumentation; protected EditText mTextView; public KeyListenerTestCase() { - super(TextViewActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); - mActivity = getActivity(); mInstrumentation = getInstrumentation(); - mTextView = (EditText) mActivity.findViewById(R.id.textview); - - mActivity.runOnUiThread(new Runnable() { - public void run() { - // Ensure that the screen is on for this test. - mTextView.setKeepScreenOn(true); - } - }); - - assertTrue(mActivity.waitForWindowFocus(5 * DateUtils.SECOND_IN_MILLIS)); + mTextView = new EditText(mInstrumentation.getContext()); } protected static KeyEvent getKey(int keycode, int metaState) { diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index 00e8c05c21c3..ff90160b8855 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -98,6 +98,8 @@ void DeferredLayerUpdater::apply() { mUpdateTexImage = false; doUpdateTexImage(); } + GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget(); + static_cast<GlLayer*>(mLayer)->setRenderTarget(renderTarget); } if (mTransform) { mLayer->getTransform().load(*mTransform); @@ -140,12 +142,8 @@ void DeferredLayerUpdater::doUpdateTexImage() { } #endif mSurfaceTexture->getTransformMatrix(transform); - GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget(); - LOG_ALWAYS_FATAL_IF(renderTarget != GL_TEXTURE_2D && renderTarget != GL_TEXTURE_EXTERNAL_OES, - "doUpdateTexImage target %x, 2d %x, EXT %x", - renderTarget, GL_TEXTURE_2D, GL_TEXTURE_EXTERNAL_OES); - updateLayer(forceFilter, renderTarget, transform); + updateLayer(forceFilter, transform); } } @@ -155,28 +153,17 @@ void DeferredLayerUpdater::doUpdateVkTexImage() { mLayer->getApi(), Layer::Api::OpenGL, Layer::Api::Vulkan); static const mat4 identityMatrix; - updateLayer(false, GL_NONE, identityMatrix.data); + updateLayer(false, identityMatrix.data); VkLayer* vkLayer = static_cast<VkLayer*>(mLayer); vkLayer->updateTexture(); } -void DeferredLayerUpdater::updateLayer(bool forceFilter, GLenum renderTarget, - const float* textureTransform) { +void DeferredLayerUpdater::updateLayer(bool forceFilter, const float* textureTransform) { mLayer->setBlend(mBlend); mLayer->setForceFilter(forceFilter); mLayer->setSize(mWidth, mHeight); mLayer->getTexTransform().load(textureTransform); - - if (mLayer->getApi() == Layer::Api::OpenGL) { - GlLayer* glLayer = static_cast<GlLayer*>(mLayer); - if (renderTarget != glLayer->getRenderTarget()) { - glLayer->setRenderTarget(renderTarget); - glLayer->bindTexture(); - glLayer->setFilter(GL_NEAREST, false, true); - glLayer->setWrap(GL_CLAMP_TO_EDGE, false, true); - } - } } void DeferredLayerUpdater::detachSurfaceTexture() { diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index 67173615d411..6164e4744da5 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -101,7 +101,7 @@ public: void detachSurfaceTexture(); - void updateLayer(bool forceFilter, GLenum renderTarget, const float* textureTransform); + void updateLayer(bool forceFilter, const float* textureTransform); void destroyLayer(); diff --git a/libs/hwui/GlLayer.cpp b/libs/hwui/GlLayer.cpp index aacad548c68b..070e95432a11 100644 --- a/libs/hwui/GlLayer.cpp +++ b/libs/hwui/GlLayer.cpp @@ -55,9 +55,15 @@ void GlLayer::onGlContextLost() { texture.deleteTexture(); } -void GlLayer::bindTexture() const { - if (texture.mId) { - caches.textureState().bindTexture(texture.target(), texture.mId); +void GlLayer::setRenderTarget(GLenum renderTarget) { + if (renderTarget != getRenderTarget()) { + // new render target: bind with new target, and update filter/wrap + texture.mTarget = renderTarget; + if (texture.mId) { + caches.textureState().bindTexture(texture.target(), texture.mId); + } + texture.setFilter(GL_NEAREST, false, true); + texture.setWrap(GL_CLAMP_TO_EDGE, false, true); } } diff --git a/libs/hwui/GlLayer.h b/libs/hwui/GlLayer.h index 85ddaff99503..20aaf4a35ac1 100644 --- a/libs/hwui/GlLayer.h +++ b/libs/hwui/GlLayer.h @@ -68,23 +68,12 @@ public: return texture.target(); } - inline void setRenderTarget(GLenum renderTarget) { - texture.mTarget = renderTarget; - } - inline bool isRenderable() const { return texture.target() != GL_NONE; } - void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) { - texture.setWrap(wrap, bindTexture, force); - } - - void setFilter(GLenum filter, bool bindTexture = false, bool force = false) { - texture.setFilter(filter, bindTexture, force); - } + void setRenderTarget(GLenum renderTarget); - void bindTexture() const; void generateTexture(); /** diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index de80ee3ca229..f2b0eb3f8d9e 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -128,6 +128,8 @@ bool SkiaOpenGLPipeline::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBi return false; } + // acquire most recent buffer for drawing + deferredLayer->updateTexImage(); deferredLayer->apply(); SkCanvas canvas(*bitmap); diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp index 8a5d9ccf759c..acd6110e89a5 100644 --- a/libs/hwui/renderthread/OpenGLPipeline.cpp +++ b/libs/hwui/renderthread/OpenGLPipeline.cpp @@ -120,6 +120,8 @@ bool OpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& sc bool OpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { ATRACE_CALL(); + // acquire most recent buffer for drawing + layer->updateTexImage(); layer->apply(); return OpenGLReadbackImpl::copyLayerInto(mRenderThread, static_cast<GlLayer&>(*layer->backingLayer()), bitmap); diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 3e52c39b5c1f..64ec58d0adab 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -74,7 +74,11 @@ sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater( layerUpdater->setTransform(&transform); // updateLayer so it's ready to draw - layerUpdater->updateLayer(true, GL_TEXTURE_EXTERNAL_OES, Matrix4::identity().data); + layerUpdater->updateLayer(true, Matrix4::identity().data); + if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) { + static_cast<GlLayer*>(layerUpdater->backingLayer())->setRenderTarget( + GL_TEXTURE_EXTERNAL_OES); + } return layerUpdater; } diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp index 1ef9dba07c6a..87d897ee6a7b 100644 --- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp +++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp @@ -44,7 +44,12 @@ RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) { // push the deferred updates to the layer Matrix4 scaledMatrix; scaledMatrix.loadScale(0.5, 0.5, 0.0); - layerUpdater->updateLayer(true, GL_TEXTURE_EXTERNAL_OES, scaledMatrix.data); + layerUpdater->updateLayer(true, scaledMatrix.data); + if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) { + GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer()); + glLayer->setRenderTarget(GL_TEXTURE_EXTERNAL_OES); + } + // the backing layer should now have all the properties applied. if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) { diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java index d6bf421ffa9f..228a6de6907c 100644 --- a/media/java/android/media/MediaHTTPConnection.java +++ b/media/java/android/media/MediaHTTPConnection.java @@ -61,8 +61,9 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { private final static int MAX_REDIRECTS = 20; public MediaHTTPConnection() { - if (CookieHandler.getDefault() == null) { - CookieHandler.setDefault(new CookieManager()); + CookieManager cookieManager = (CookieManager)CookieHandler.getDefault(); + if (cookieManager == null) { + Log.w(TAG, "MediaHTTPConnection: Unexpected. No CookieManager found."); } native_setup(); diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java index 52a68bfd96f9..b678630400cb 100644 --- a/media/java/android/media/MediaHTTPService.java +++ b/media/java/android/media/MediaHTTPService.java @@ -19,25 +19,78 @@ package android.media; import android.os.IBinder; import android.util.Log; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookieStore; +import java.net.HttpCookie; +import java.util.List; + /** @hide */ public class MediaHTTPService extends IMediaHTTPService.Stub { private static final String TAG = "MediaHTTPService"; + private List<HttpCookie> mCookies; + private Boolean mCookieStoreInitialized = new Boolean(false); - public MediaHTTPService() { + public MediaHTTPService(List<HttpCookie> cookies) { + mCookies = cookies; + Log.v(TAG, "MediaHTTPService(" + this + "): Cookies: " + cookies); } public IMediaHTTPConnection makeHTTPConnection() { + + synchronized (mCookieStoreInitialized) { + // Only need to do it once for all connections + if ( !mCookieStoreInitialized ) { + CookieManager cookieManager = (CookieManager)CookieHandler.getDefault(); + if (cookieManager == null) { + cookieManager = new CookieManager(); + CookieHandler.setDefault(cookieManager); + Log.v(TAG, "makeHTTPConnection: CookieManager created: " + cookieManager); + } + else { + Log.v(TAG, "makeHTTPConnection: CookieManager(" + cookieManager + ") exists."); + } + + // Applying the bootstrapping cookies + if ( mCookies != null ) { + CookieStore store = cookieManager.getCookieStore(); + for ( HttpCookie cookie : mCookies ) { + try { + store.add(null, cookie); + } catch ( Exception e ) { + Log.v(TAG, "makeHTTPConnection: CookieStore.add" + e); + } + //for extended debugging when needed + //Log.v(TAG, "MediaHTTPConnection adding Cookie[" + cookie.getName() + + // "]: " + cookie); + } + } // mCookies + + mCookieStoreInitialized = true; + + Log.v(TAG, "makeHTTPConnection(" + this + "): cookieManager: " + cookieManager + + " Cookies: " + mCookies); + } // mCookieStoreInitialized + } // synchronized + return new MediaHTTPConnection(); } /* package private */static IBinder createHttpServiceBinderIfNecessary( String path) { + return createHttpServiceBinderIfNecessary(path, null); + } + + // when cookies are provided + static IBinder createHttpServiceBinderIfNecessary( + String path, List<HttpCookie> cookies) { if (path.startsWith("http://") || path.startsWith("https://")) { - return (new MediaHTTPService()).asBinder(); + return (new MediaHTTPService(cookies)).asBinder(); } else if (path.startsWith("widevine://")) { Log.d(TAG, "Widevine classic is no longer supported"); } return null; } + } diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index c5a47ecd2bb6..5008a5f8c5c2 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -73,6 +73,7 @@ import java.lang.Runnable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; +import java.net.HttpCookie; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.URL; @@ -80,6 +81,7 @@ import java.nio.ByteOrder; import java.util.Arrays; import java.util.BitSet; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; @@ -638,6 +640,8 @@ public class MediaPlayer extends PlayerBase private UUID mDrmUUID; private final Object mDrmLock = new Object(); private DrmInfo mDrmInfo; + private MediaDrm mDrmObj; + private byte[] mDrmSessionId; private boolean mDrmInfoResolved; private boolean mActiveDrmScheme; private boolean mDrmConfigAllowed; @@ -998,7 +1002,7 @@ public class MediaPlayer extends PlayerBase */ public void setDataSource(@NonNull Context context, @NonNull Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { - setDataSource(context, uri, null); + setDataSource(context, uri, null, null); } /** @@ -1011,11 +1015,13 @@ public class MediaPlayer extends PlayerBase * changed with key/value pairs through the headers parameter with * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value * to disallow or allow cross domain redirection. + * The headers must not include cookies. Instead, use the cookies param. + * @param cookies the cookies to be sent together with the request * @throws IllegalStateException if it is called in an invalid state */ public void setDataSource(@NonNull Context context, @NonNull Uri uri, - @Nullable Map<String, String> headers) throws IOException, IllegalArgumentException, - SecurityException, IllegalStateException { + @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { // The context and URI usually belong to the calling user. Get a resolver for that user // and strip out the userId from the URI if present. final ContentResolver resolver = context.getContentResolver(); @@ -1036,18 +1042,36 @@ public class MediaPlayer extends PlayerBase } else if (attemptDataSource(resolver, actualUri)) { return; } else { - setDataSource(uri.toString(), headers); + setDataSource(uri.toString(), headers, cookies); } } else { // Try requested Uri locally first, or fallback to media server if (attemptDataSource(resolver, uri)) { return; } else { - setDataSource(uri.toString(), headers); + setDataSource(uri.toString(), headers, cookies); } } } + /** + * Sets the data source as a content Uri. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play + * @param headers the headers to be sent together with the request for the data + * Note that the cross domain redirection is allowed by default, but that can be + * changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value + * to disallow or allow cross domain redirection. + * @throws IllegalStateException if it is called in an invalid state + */ + public void setDataSource(@NonNull Context context, @NonNull Uri uri, + @Nullable Map<String, String> headers) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { + setDataSource(context, uri, headers, null); + } + private boolean attemptDataSource(ContentResolver resolver, Uri uri) { try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) { setDataSource(afd); @@ -1085,6 +1109,11 @@ public class MediaPlayer extends PlayerBase * @hide pending API council */ public void setDataSource(String path, Map<String, String> headers) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { + setDataSource(path, headers, null); + } + + private void setDataSource(String path, Map<String, String> headers, List<HttpCookie> cookies) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { String[] keys = null; @@ -1101,10 +1130,11 @@ public class MediaPlayer extends PlayerBase ++i; } } - setDataSource(path, keys, values); + setDataSource(path, keys, values, cookies); } - private void setDataSource(String path, String[] keys, String[] values) + private void setDataSource(String path, String[] keys, String[] values, + List<HttpCookie> cookies) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { final Uri uri = Uri.parse(path); final String scheme = uri.getScheme(); @@ -1113,7 +1143,7 @@ public class MediaPlayer extends PlayerBase } else if (scheme != null) { // handle non-file sources nativeSetDataSource( - MediaHTTPService.createHttpServiceBinderIfNecessary(path), + MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies), path, keys, values); @@ -1932,6 +1962,7 @@ public class MediaPlayer extends PlayerBase mOnSubtitleDataListener = null; // Modular DRM clean up + mOnDrmConfigListener = null; mOnDrmInfoHandlerDelegate = null; mOnDrmPreparedHandlerDelegate = null; resetDrmState(); @@ -3143,7 +3174,7 @@ public class MediaPlayer extends PlayerBase onDrmInfoHandlerDelegate.notifyClient(drmInfo); } } else { - Log.w(TAG, "MEDIA_DRM_INFO msg.obj NONE; UNEXPECTED" + msg.obj); + Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj); } return; @@ -3818,17 +3849,34 @@ public class MediaPlayer extends PlayerBase * and setDrmPropertyString. * */ - public static abstract class OnDrmConfigCallback + public interface OnDrmConfigListener { /** * Called to give the app the opportunity to configure DRM before the session is created * * @param mp the {@code MediaPlayer} associated with this callback */ - public void onDrmConfig(MediaPlayer mp) {} + public void onDrmConfig(MediaPlayer mp); } /** + * Register a callback to be invoked for configuration of the DRM object before + * the session is created. + * The callback will be invoked synchronously half-way into the execution + * of {@link #prepareDrm(UUID uuid)}. + * + * @param listener the callback that will be run + */ + public void setOnDrmConfigListener(OnDrmConfigListener listener) + { + synchronized (mDrmLock) { + mOnDrmConfigListener = listener; + } // synchronized + } + + private OnDrmConfigListener mOnDrmConfigListener; + + /** * Interface definition of a callback to be invoked when the * DRM info becomes available */ @@ -4025,13 +4073,11 @@ public class MediaPlayer extends PlayerBase return drmInfo; } - private native void _prepareDrm(@NonNull byte[] uuid, int mode) - throws UnsupportedSchemeException, ResourceBusyException, NotProvisionedException; /** * Prepares the DRM for the current source * <p> - * If {@code OnDrmConfigCallback} is registered, it will be called half-way into + * If {@code OnDrmConfigListener} is registered, it will be called half-way into * preparation to allow configuration of the DRM properties before opening the * DRM session. Note that the callback is called synchronously in the thread that called * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString} @@ -4059,10 +4105,12 @@ public class MediaPlayer extends PlayerBase * @throws ResourceBusyException if required DRM resources are in use * @throws ProvisioningErrorException if provisioning is required but an attempt failed */ - public void prepareDrm(@NonNull UUID uuid, OnDrmConfigCallback configCallback) + public void prepareDrm(@NonNull UUID uuid) throws UnsupportedSchemeException, ResourceBusyException, ProvisioningErrorException { + Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigListener: " + mOnDrmConfigListener); + boolean allDoneWithoutProvisioning = false; // get a snapshot as we'll use them outside the lock OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate = null; @@ -4071,58 +4119,46 @@ public class MediaPlayer extends PlayerBase // only allowing if tied to a protected source; might releax for releasing offline keys if (mDrmInfo == null) { - final String msg = String.format("prepareDrm(%s): Wrong usage: " + - "The player must be prepared and DRM " + - "info be retrieved before this call.", uuid); + final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " + + "DRM info be retrieved before this call."; Log.e(TAG, msg); throw new IllegalStateException(msg); } if (mActiveDrmScheme) { - final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " + - "an active DRM scheme with %s.", uuid, mDrmUUID); + final String msg = "prepareDrm(): Wrong usage: There is already " + + "an active DRM scheme with " + mDrmUUID; Log.e(TAG, msg); throw new IllegalStateException(msg); } if (mPrepareDrmInProgress) { - final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " + - "a pending prepareDrm call.", uuid); + final String msg = "prepareDrm(): Wrong usage: There is already " + + "a pending prepareDrm call."; Log.e(TAG, msg); throw new IllegalStateException(msg); } if (mDrmProvisioningInProgress) { - final String msg = String.format("prepareDrm(%s): Unexpectd: Provisioning is " + - "already in progress.", uuid); + final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress."; Log.e(TAG, msg); throw new IllegalStateException(msg); } + // shouldn't need this; just for safeguard + cleanDrmObj(); + mPrepareDrmInProgress = true; // local copy while the lock is held onDrmPreparedHandlerDelegate = mOnDrmPreparedHandlerDelegate; - if (configCallback != null) { - try { - boolean allowOpenSession = false; // just pre-openSession - _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0); - } catch (IllegalStateException e) { - final String msg = String.format("prepareDrm(): Wrong usage: The player must " + - "be in prepared state to call prepareDrm()."); - Log.e(TAG, msg); - throw new IllegalStateException(msg); - } catch (NotProvisionedException e) { // the pre-config step won't raise this - final String msg = String.format("prepareDrm: Unexpected " + - "NotProvisionedException here."); - Log.e(TAG, msg); - throw new ProvisioningErrorException(msg); - } catch (Exception e) { - Log.w(TAG, String.format("prepareDrm: Exception %s", e)); - throw e; - } finally { - mPrepareDrmInProgress = false; - } + try { + // only creating the DRM object to allow pre-openSession configuration + prepareDrm_createDrmStep(uuid); + } catch (Exception e) { + Log.w(TAG, "prepareDrm(): Exception ", e); + mPrepareDrmInProgress = false; + throw e; } mDrmConfigAllowed = true; @@ -4130,51 +4166,55 @@ public class MediaPlayer extends PlayerBase // call the callback outside the lock - if (configCallback != null) { - configCallback.onDrmConfig(this); + if (mOnDrmConfigListener != null) { + mOnDrmConfigListener.onDrmConfig(this); } synchronized (mDrmLock) { mDrmConfigAllowed = false; + boolean earlyExit = false; try { - boolean allowOpenSession = true; // all in - _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0); + prepareDrm_openSessionStep(uuid); mDrmUUID = uuid; mActiveDrmScheme = true; - mPrepareDrmInProgress = false; - allDoneWithoutProvisioning = true; } catch (IllegalStateException e) { - final String msg = String.format("prepareDrm(%s): Wrong usage: The player must be" + - " in prepared state to call prepareDrm().", uuid); + final String msg = "prepareDrm(): Wrong usage: The player must be " + + "in the prepared state to call prepareDrm()."; Log.e(TAG, msg); + earlyExit = true; throw new IllegalStateException(msg); } catch (NotProvisionedException e) { - Log.w(TAG, String.format("prepareDrm: NotProvisionedException")); + Log.w(TAG, "prepareDrm: NotProvisionedException"); - // handle provisioning internally + // handle provisioning internally; it'll reset mPrepareDrmInProgress boolean result = HandleProvisioninig(uuid); // if blocking mode, we're already done; // if non-blocking mode, we attempted to launch background provisioning if (result == false) { - final String msg = - String.format("prepareDrm: Provisioning was required but failed."); + final String msg = "prepareDrm: Provisioning was required but failed."; Log.e(TAG, msg); + earlyExit = true; throw new ProvisioningErrorException(msg); } - // nothing else to do; // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup } catch (Exception e) { - Log.w(TAG, String.format("prepareDrm: Exception %s", e)); + Log.e(TAG, "prepareDrm: Exception " + e); + earlyExit = true; throw e; } finally { - mPrepareDrmInProgress = false; - } + if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception + mPrepareDrmInProgress = false; + } + if (earlyExit) { // cleaning up object if didn't succeed + cleanDrmObj(); + } + } // finally } // synchronized @@ -4197,25 +4237,33 @@ public class MediaPlayer extends PlayerBase public void releaseDrm() throws NoDrmSchemeException { + Log.v(TAG, "releaseDrm:"); + synchronized (mDrmLock) { if (!mActiveDrmScheme) { - Log.e(TAG, String.format("releaseDrm(%s): No active DRM scheme to release.")); + Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); throw new NoDrmSchemeException("releaseDrm: No active DRM scheme to release."); - } else { + } + + try { + // we don't have the player's state in this layer. The below call raises + // exception if we're in a non-stopped/idle state. + + // for cleaning native/mediaserver crypto object _releaseDrm(); + // for cleaning client-side MediaDrm object; only called if above has succeeded + cleanDrmObj(); + mActiveDrmScheme = false; + } catch (Exception e) { + Log.w(TAG, "releaseDrm: Exception ", e); + throw e; } } // synchronized } - @NonNull - private native MediaDrm.KeyRequest _getKeyRequest(@NonNull byte[] scope, - @Nullable String mimeType, @MediaDrm.KeyType int keyType, - @Nullable Map<String, String> optionalParameters) - throws NotProvisionedException; - /** * A key request/response exchange occurs between the app and a license server * to obtain or release keys used to decrypt encrypted content. @@ -4256,20 +4304,42 @@ public class MediaPlayer extends PlayerBase @MediaDrm.KeyType int keyType, @Nullable Map<String, String> optionalParameters) throws NoDrmSchemeException { + Log.v(TAG, "getKeyRequest: " + + " scope: " + scope + " mimeType: " + mimeType + + " keyType: " + keyType + " optionalParameters: " + optionalParameters); + synchronized (mDrmLock) { if (!mActiveDrmScheme) { - Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException")); + Log.e(TAG, "getKeyRequest NoDrmSchemeException"); throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first."); } try { - return _getKeyRequest(scope, mimeType, keyType, optionalParameters); + byte[] scopeOut = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + scope; // keySetId for KEY_TYPE_RELEASE + + byte[] initData = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? + scope : // initData for KEY_TYPE_STREAMING/OFFLINE + null; // not used for KEY_TYPE_RELEASE + + HashMap<String, String> hmapOptionalParameters = + (optionalParameters != null) ? + new HashMap<String, String>(optionalParameters) : + null; + + MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scopeOut, initData, mimeType, + keyType, hmapOptionalParameters); + Log.v(TAG, "getKeyRequest: --> request: " + request); + + return request; + } catch (NotProvisionedException e) { - Log.w(TAG, String.format("getKeyRequest NotProvisionedException: " + - "Unexpected. Shouldn't have reached here.")); + Log.w(TAG, "getKeyRequest NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); throw new IllegalStateException("getKeyRequest: Unexpected provisioning error."); } catch (Exception e) { - Log.w(TAG, String.format("getKeyRequest Exception %s", e)); + Log.w(TAG, "getKeyRequest Exception " + e); throw e; } @@ -4277,10 +4347,6 @@ public class MediaPlayer extends PlayerBase } - @Nullable - private native byte[] _provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) - throws DeniedByServerException; - /** * A key response is received from the license server by the app, then it is * provided to the DRM engine plugin using provideKeyResponse. When the @@ -4303,25 +4369,41 @@ public class MediaPlayer extends PlayerBase public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) throws NoDrmSchemeException, DeniedByServerException { + Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response); + synchronized (mDrmLock) { if (!mActiveDrmScheme) { - Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException")); + Log.e(TAG, "getKeyRequest NoDrmSchemeException"); throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first."); } try { - return _provideKeyResponse(keySetId, response); + byte[] scope = (keySetId == null) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + keySetId; // keySetId for KEY_TYPE_RELEASE + + byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); + + Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response + + " --> " + keySetResult); + + + return keySetResult; + + } catch (NotProvisionedException e) { + Log.w(TAG, "provideKeyResponse NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); + throw new IllegalStateException("provideKeyResponse: " + + "Unexpected provisioning error."); } catch (Exception e) { - Log.w(TAG, String.format("provideKeyResponse Exception %s", e)); + Log.w(TAG, "provideKeyResponse Exception " + e); throw e; } } // synchronized } - private native void _restoreKeys(@NonNull byte[] keySetId); - /** * Restore persisted offline keys into a new session. keySetId identifies the * keys to load, obtained from a prior call to {@link #provideKeyResponse}. @@ -4331,17 +4413,19 @@ public class MediaPlayer extends PlayerBase public void restoreKeys(@NonNull byte[] keySetId) throws NoDrmSchemeException { + Log.v(TAG, "restoreKeys: keySetId: " + keySetId); + synchronized (mDrmLock) { if (!mActiveDrmScheme) { - Log.w(TAG, String.format("restoreKeys NoDrmSchemeException")); + Log.w(TAG, "restoreKeys NoDrmSchemeException"); throw new NoDrmSchemeException("restoreKeys: Has to set a DRM scheme first."); } try { - _restoreKeys(keySetId); + mDrmObj.restoreKeys(mDrmSessionId, keySetId); } catch (Exception e) { - Log.w(TAG, String.format("restoreKeys Exception %s", e)); + Log.w(TAG, "restoreKeys Exception " + e); throw e; } @@ -4349,9 +4433,6 @@ public class MediaPlayer extends PlayerBase } - @NonNull - private native String _getDrmPropertyString(@NonNull String propertyName); - /** * Read a DRM engine plugin String property value, given the property name string. * <p> @@ -4365,26 +4446,29 @@ public class MediaPlayer extends PlayerBase public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName) throws NoDrmSchemeException { + Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); + String value; synchronized (mDrmLock) { if (!mActiveDrmScheme && !mDrmConfigAllowed) { - Log.w(TAG, String.format("getDrmPropertyString NoDrmSchemeException")); + Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); throw new NoDrmSchemeException("getDrmPropertyString: Has to prepareDrm() first."); } try { - value = _getDrmPropertyString(propertyName); + value = mDrmObj.getPropertyString(propertyName); } catch (Exception e) { - Log.w(TAG, String.format("getDrmPropertyString Exception %s", e)); + Log.w(TAG, "getDrmPropertyString Exception " + e); throw e; } } // synchronized + Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value); + return value; } - private native void _setDrmPropertyString(@NonNull String propertyName, @NonNull String value); /** * Set a DRM engine plugin String property value. @@ -4400,17 +4484,19 @@ public class MediaPlayer extends PlayerBase @NonNull String value) throws NoDrmSchemeException { + Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); + synchronized (mDrmLock) { if ( !mActiveDrmScheme && !mDrmConfigAllowed ) { - Log.w(TAG, String.format("setDrmPropertyString NoDrmSchemeException")); + Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); throw new NoDrmSchemeException("setDrmPropertyString: Has to prepareDrm() first."); } try { - _setDrmPropertyString(propertyName, value); + mDrmObj.setPropertyString(propertyName, value); } catch ( Exception e ) { - Log.w(TAG, String.format("setDrmPropertyString Exception %s", e)); + Log.w(TAG, "setDrmPropertyString Exception " + e); throw e; } } // synchronized @@ -4577,8 +4663,47 @@ public class MediaPlayer extends PlayerBase } } + + private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId); + // Modular DRM helpers + private void prepareDrm_createDrmStep(@NonNull UUID uuid) + throws UnsupportedSchemeException { + Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); + + try { + mDrmObj = new MediaDrm(uuid); + Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); + } catch (Exception e) { // UnsupportedSchemeException + Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); + throw e; + } + } + + private void prepareDrm_openSessionStep(@NonNull UUID uuid) + throws NotProvisionedException, ResourceBusyException { + Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); + + // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do + // it anyway so it raises provisioning error if needed. We'd rather handle provisioning + // at prepareDrm/openSession rather than getKeyRequest/provideKeyResponse + try { + mDrmSessionId = mDrmObj.openSession(); + Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); + + // Sending it down to native/mediaserver to create the crypto object + // This call could simply fail due to bad player state, e.g., after start(). + _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); + Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded"); + + } catch (Exception e) { //ResourceBusyException, NotProvisionedException + Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); + throw e; + } + + } + private class ProvisioningThread extends Thread { public static final int TIMEOUT_MS = 60000; @@ -4605,7 +4730,7 @@ public class MediaPlayer extends PlayerBase urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); this.uuid = uuid; - Log.v(TAG, String.format("HandleProvisioninig: Thread is initialised url: %s", urlStr)); + Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr); return this; } @@ -4625,30 +4750,27 @@ public class MediaPlayer extends PlayerBase connection.connect(); response = Streams.readFully(connection.getInputStream()); - Log.v(TAG, String.format("HandleProvisioninig: Thread run response %d %s", - response.length, response)); + Log.v(TAG, "HandleProvisioninig: Thread run: response " + + response.length + " " + response); } catch (Exception e) { - Log.w(TAG, String.format("HandleProvisioninig: Thread run connect %s url: %s", - e, url)); + Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url); } finally { connection.disconnect(); } } catch (Exception e) { - Log.w(TAG, String.format("HandleProvisioninig: Thread run openConnection %s", e)); + Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e); } if (response != null) { try { - MediaDrm drm = new MediaDrm(uuid); - drm.provideProvisionResponse(response); - drm.release(); - Log.v(TAG, String.format("HandleProvisioninig: Thread run " + - "newDrm+provideProvisionResponse SUCCEEDED!")); + mDrmObj.provideProvisionResponse(response); + Log.v(TAG, "HandleProvisioninig: Thread run: " + + "provideProvisionResponse SUCCEEDED!"); provisioningSucceeded = true; } catch (Exception e) { - Log.w(TAG, String.format("HandleProvisioninig: Thread run " + - "newDrm+provideProvisionResponse %s", e)); + Log.w(TAG, "HandleProvisioninig: Thread run: " + + "provideProvisionResponse " + e); } } @@ -4662,7 +4784,10 @@ public class MediaPlayer extends PlayerBase } mediaPlayer.mDrmProvisioningInProgress = false; mediaPlayer.mPrepareDrmInProgress = false; - } + if (!succeeded) { + cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock + } + } // synchronized // calling the callback outside the lock onDrmPreparedHandlerDelegate.notifyClient(succeeded); @@ -4674,6 +4799,9 @@ public class MediaPlayer extends PlayerBase } mediaPlayer.mDrmProvisioningInProgress = false; mediaPlayer.mPrepareDrmInProgress = false; + if (!succeeded) { + cleanDrmObj(); // cleaning up if it hasn't gone through + } } finished = true; @@ -4686,24 +4814,18 @@ public class MediaPlayer extends PlayerBase // the lock is already held by the caller if (mDrmProvisioningInProgress) { - Log.e(TAG, String.format("HandleProvisioninig: Unexpected mDrmProvisioningInProgress")); + Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress"); return false; } - MediaDrm.ProvisionRequest provReq = null; - try { - MediaDrm drm = new MediaDrm(uuid); - provReq = drm.getProvisionRequest(); - drm.release(); - } catch (Exception e) { - Log.e(TAG, String.format("HandleProvisioninig: getProvisionRequest failed with %s", e)); + MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); + if (provReq == null) { + Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null."); return false; } - Log.v(TAG, String.format("HandleProvisioninig provReq: data %s url %s", - (provReq != null) ? provReq.getData() : "-", - (provReq != null) ? provReq.getDefaultUrl() : "://") - ); + Log.v(TAG, "HandleProvisioninig provReq " + + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); // networking in a background thread mDrmProvisioningInProgress = true; @@ -4721,7 +4843,7 @@ public class MediaPlayer extends PlayerBase try { mDrmProvisioningThread.join(); } catch (Exception e) { - Log.w(TAG, String.format("HandleProvisioninig: Thread.join Exception %s", e)); + Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e); } result = mDrmProvisioningThread.succeeded(); // no longer need the thread @@ -4733,19 +4855,21 @@ public class MediaPlayer extends PlayerBase private boolean resumePrepareDrm(UUID uuid) { + Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); + // mDrmLock is guaranteed to be held boolean success = false; try { - boolean allowOpenSession = true; // resuming - _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0); + // resuming + prepareDrm_openSessionStep(uuid); mDrmUUID = uuid; mActiveDrmScheme = true; success = true; } catch (Exception e) { - Log.w(TAG, String.format("HandleProvisioninig: " + - "Thread run _prepareDrm resume failed with %s", e)); + Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e); + // mDrmObj clean up is done by the caller } return success; @@ -4754,6 +4878,12 @@ public class MediaPlayer extends PlayerBase private void resetDrmState() { synchronized (mDrmLock) { + Log.v(TAG, "resetDrmState: " + + " mDrmInfo=" + mDrmInfo + + " mDrmProvisioningThread=" + mDrmProvisioningThread + + " mPrepareDrmInProgress=" + mPrepareDrmInProgress + + " mActiveDrmScheme=" + mActiveDrmScheme); + mDrmInfoResolved = false; mDrmInfo = null; @@ -4763,15 +4893,33 @@ public class MediaPlayer extends PlayerBase mDrmProvisioningThread.join(); } catch (InterruptedException e) { - Log.w(TAG, String.format("resetDrmState: ProvThread.join Exception %s", e)); + Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); } mDrmProvisioningThread = null; } mPrepareDrmInProgress = false; + mActiveDrmScheme = false; + + cleanDrmObj(); } // synchronized } + private void cleanDrmObj() + { + // the caller holds mDrmLock + Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); + + if (mDrmSessionId != null) { + mDrmObj.closeSession(mDrmSessionId); + mDrmSessionId = null; + } + if (mDrmObj != null) { + mDrmObj.release(); + mDrmObj = null; + } + } + private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { long msb = uuid.getMostSignificantBits(); long lsb = uuid.getLeastSignificantBits(); diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index ddbd542ec037..45f88f3b2ac6 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -1977,6 +1977,24 @@ public final class TvContract { public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited"; /** + * The flag indicating whether this TV program is browsable or not. + * + * <p>This column can only be set by system apps. For other applications, it is a read-only + * column. Trying to modify it may cause {@link SecurityException}. + * + * <p>A value of 1 indicates that the program is browsable and can be shown to users in + * the UI. A value of 0 indicates that the program should be hidden from users and the + * application who changes this value to 0 should send + * {@link TvInputManager#ACTION_PROGRAM_BROWSABLE_DISABLED} to the owner of the program + * to notify this change. + * + * <p>This value is set to 1 (browsable) by default. + * + * <p>Type: INTEGER (boolean) + */ + public static final String COLUMN_BROWSABLE = "browsable"; + + /** * The internal ID used by individual TV input services. * * <p>This is internal to the provider that inserted it, and should not be decoded by other diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index b630270f2616..4c2b031d8f04 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -325,23 +325,39 @@ public final class TvInputManager { "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; /** + * Action sent by the system to tell the target TV input that one of its program's browsable + * state is disabled, i.e., it will no longer be shown to users, which, for example, might + * be a result of users' interaction with UI. + * + * <p>The intent must contain the following bundle parameter: + * <ul> + * <li>{@link #EXTRA_PROGRAM_ID} the program ID as a long integer. + * </ul> + */ + public static final String ACTION_PROGRAM_BROWSABLE_DISABLED = + "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED"; + + /** * Action sent by an application telling the system to set the given channel as browsable. * * <p>The intent must contain the following bundle parameters: * <ul> - * <li>{@link #EXTRA_CHANNEL_ID} then channel ID as an integer. + * <li>{@link #EXTRA_CHANNEL_ID} the channel ID as a long integer. * <li>{@link #EXTRA_PACKAGE_NAME} the package name of the requesting application. * </ul> */ public static final String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE"; - /** The key for a bundle parameter containing a channel ID as an integer */ + /** The key for a bundle parameter containing a channel ID as a long integer */ public static final String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID"; /** The key for a bundle parameter containing a package name as a string. */ public static final String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME"; + /** The key for a bundle parameter containing a program ID as a long integer */ + public static final String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID"; + private final ITvInputManager mService; private final Object mLock = new Object(); diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index c941766c07d6..636727eb0ac0 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -58,86 +58,20 @@ #include "android_util_Binder.h" // Modular DRM begin -#include <media/drm/DrmAPI.h> - #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ LOG_FATAL_IF(! (var), "Unable to find class " className); -#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ -var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ -LOG_FATAL_IF(! (var), "Unable to find field " fieldName); - #define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find method " fieldName); -#define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ -var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \ -LOG_FATAL_IF(! (var), "Unable to find field " fieldName); - - -// TODO: investigate if these can be shared with their MediaDrm counterparts -struct RequestFields { - jfieldID data; - jfieldID defaultUrl; - jfieldID requestType; -}; - -struct HashmapFields { - jmethodID init; - jmethodID get; - jmethodID put; - jmethodID entrySet; -}; - -struct SetFields { - jmethodID iterator; -}; - -struct IteratorFields { - jmethodID next; - jmethodID hasNext; -}; - -struct EntryFields { - jmethodID getKey; - jmethodID getValue; -}; - -struct KeyTypes { - jint kKeyTypeStreaming; - jint kKeyTypeOffline; - jint kKeyTypeRelease; -}; - -static KeyTypes gKeyTypes; - -struct KeyRequestTypes { - jint kKeyRequestTypeInitial; - jint kKeyRequestTypeRenewal; - jint kKeyRequestTypeRelease; -}; - -static KeyRequestTypes gKeyRequestTypes; - struct StateExceptionFields { jmethodID init; jclass classId; }; -struct drm_fields_t { - RequestFields keyRequest; - HashmapFields hashmap; - SetFields set; - IteratorFields iterator; - EntryFields entry; - StateExceptionFields stateException; - jclass stringClassId; -}; - -static drm_fields_t gFields; - +static StateExceptionFields gStateExceptionFields; // Modular DRM end // ---------------------------------------------------------------------------- @@ -1041,50 +975,14 @@ android_media_MediaPlayer_native_init(JNIEnv *env) gBufferingParamsFields.init(env); // Modular DRM - FIND_CLASS(clazz, "android/media/MediaDrm"); - if (clazz) { - jfieldID field; - GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I"); - gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I"); - gKeyTypes.kKeyTypeOffline = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I"); - gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field); - - env->DeleteLocalRef(clazz); - } else { - ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't " - "get clazz android/media/MediaDrm"); - } - - FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); - if (clazz) { - GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B"); - GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;"); - GET_FIELD_ID(gFields.keyRequest.requestType, clazz, "mRequestType", "I"); - - jfieldID field; - GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I"); - gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I"); - gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field); - GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I"); - gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field); - - env->DeleteLocalRef(clazz); - } else { - ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't " - "get clazz android/media/MediaDrm$KeyRequest"); - } - FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException"); if (clazz) { - GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V"); - gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); + GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V"); + gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); env->DeleteLocalRef(clazz); } else { - ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't " + ALOGE("JNI android_media_MediaPlayer_native_init couldn't " "get clazz android/media/MediaDrm$MediaDrmStateException"); } @@ -1315,8 +1213,8 @@ static void throwDrmStateException(JNIEnv *env, const char *msg, status_t err) { ALOGE("Illegal DRM state exception: %s (%d)", msg, err); - jobject exception = env->NewObject(gFields.stateException.classId, - gFields.stateException.init, static_cast<int>(err), + jobject exception = env->NewObject(gStateExceptionFields.classId, + gStateExceptionFields.init, static_cast<int>(err), env->NewStringUTF(msg)); env->Throw(static_cast<jthrowable>(exception)); } @@ -1393,18 +1291,6 @@ static bool throwDrmExceptionAsNecessary(JNIEnv *env, status_t err, const char * return false; } -// TODO: investigate if these can be shared with their MediaDrm counterparts -static jbyteArray VectorToJByteArray(JNIEnv *env, Vector<uint8_t> const &vector) -{ - size_t length = vector.size(); - jbyteArray result = env->NewByteArray(length); - if (result != NULL) { - env->SetByteArrayRegion(result, 0, length, (jbyte *)vector.array()); - } - return result; -} - -// TODO: investigate if these can be shared with their MediaDrm counterparts static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) { Vector<uint8_t> vector; @@ -1414,74 +1300,8 @@ static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArr return vector; } -// TODO: investigate if these can be shared with their MediaDrm counterparts -static String8 JStringToString8(JNIEnv *env, jstring const &jstr) -{ - String8 result; - - const char *s = env->GetStringUTFChars(jstr, NULL); - if (s) { - result = s; - env->ReleaseStringUTFChars(jstr, s); - } - return result; -} - -// TODO: investigate if these can be shared with their MediaDrm counterparts -static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, - jobject &hashMap, bool* pIsOK) -{ - jclass clazz = gFields.stringClassId; - KeyedVector<String8, String8> keyedVector; - *pIsOK = true; - - jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet); - if (entrySet) { - jobject iterator = env->CallObjectMethod(entrySet, gFields.set.iterator); - if (iterator) { - jboolean hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext); - while (hasNext) { - jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next); - if (entry) { - jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey); - if (obj == NULL || !env->IsInstanceOf(obj, clazz)) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "HashMap key is not a String"); - env->DeleteLocalRef(entry); - *pIsOK = false; - break; - } - jstring jkey = static_cast<jstring>(obj); - - obj = env->CallObjectMethod(entry, gFields.entry.getValue); - if (obj == NULL || !env->IsInstanceOf(obj, clazz)) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "HashMap value is not a String"); - env->DeleteLocalRef(entry); - *pIsOK = false; - break; - } - jstring jvalue = static_cast<jstring>(obj); - - String8 key = JStringToString8(env, jkey); - String8 value = JStringToString8(env, jvalue); - keyedVector.add(key, value); - - env->DeleteLocalRef(jkey); - env->DeleteLocalRef(jvalue); - hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext); - } - env->DeleteLocalRef(entry); - } - env->DeleteLocalRef(iterator); - } - env->DeleteLocalRef(entrySet); - } - return keyedVector; -} - static void android_media_MediaPlayer_prepareDrm(JNIEnv *env, jobject thiz, - jbyteArray uuidObj, jint mode) + jbyteArray uuidObj, jbyteArray drmSessionIdObj) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL) { @@ -1504,13 +1324,23 @@ static void android_media_MediaPlayer_prepareDrm(JNIEnv *env, jobject thiz, return; } - status_t err = mp->prepareDrm(uuid.array(), mode); + Vector<uint8_t> drmSessionId = JByteArrayToVector(env, drmSessionIdObj); + + if (drmSessionId.size() == 0) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + "empty drmSessionId"); + return; + } + + status_t err = mp->prepareDrm(uuid.array(), drmSessionId); if (err != OK) { if (err == INVALID_OPERATION) { jniThrowException( env, "java/lang/IllegalStateException", - "The player is not prepared yet."); + "The player must be in prepared state."); } else if (err == ERROR_DRM_CANNOT_HANDLE) { jniThrowException( env, @@ -1536,210 +1366,9 @@ static void android_media_MediaPlayer_releaseDrm(JNIEnv *env, jobject thiz) jniThrowException( env, "java/lang/IllegalStateException", - "The player is not prepared yet."); - } - } -} - -static jobject android_media_MediaPlayer_getKeyRequest(JNIEnv *env, jobject thiz, jbyteArray jscope, - jstring jmimeType, jint jkeyType, jobject joptParams) -{ - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - Vector<uint8_t> scope; - if (jscope != NULL) { - scope = JByteArrayToVector(env, jscope); - } - - String8 mimeType; - if (jmimeType != NULL) { - mimeType = JStringToString8(env, jmimeType); - } - - DrmPlugin::KeyType keyType; - if (jkeyType == gKeyTypes.kKeyTypeStreaming) { - keyType = DrmPlugin::kKeyType_Streaming; - } else if (jkeyType == gKeyTypes.kKeyTypeOffline) { - keyType = DrmPlugin::kKeyType_Offline; - } else if (jkeyType == gKeyTypes.kKeyTypeRelease) { - keyType = DrmPlugin::kKeyType_Release; - } else { - jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType"); - return NULL; - } - - KeyedVector<String8, String8> optParams; - if (joptParams != NULL) { - bool isOK; - optParams = HashMapToKeyedVector(env, joptParams, &isOK); - if (!isOK) { - return NULL; - } - } - - Vector<uint8_t> request; - String8 defaultUrl; - DrmPlugin::KeyRequestType keyRequestType; - status_t err = mp->getKeyRequest(scope, mimeType, keyType, optParams, request, defaultUrl, - keyRequestType); - - if (throwDrmExceptionAsNecessary(env, err, "Failed to get key request")) { - return NULL; - } - - ALOGV("JNI getKeyRequest err %d request %d url %s keyReqType %d", - err, (int)request.size(), defaultUrl.string(), (int)keyRequestType); - - // Fill out return obj - jclass clazz; - FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); - - jobject keyObj = NULL; - - if (clazz) { - keyObj = env->AllocObject(clazz); - jbyteArray jrequest = VectorToJByteArray(env, request); - env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest); - - jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string()); - env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl); - - switch (keyRequestType) { - case DrmPlugin::kKeyRequestType_Initial: - env->SetIntField(keyObj, gFields.keyRequest.requestType, - gKeyRequestTypes.kKeyRequestTypeInitial); - break; - case DrmPlugin::kKeyRequestType_Renewal: - env->SetIntField(keyObj, gFields.keyRequest.requestType, - gKeyRequestTypes.kKeyRequestTypeRenewal); - break; - case DrmPlugin::kKeyRequestType_Release: - env->SetIntField(keyObj, gFields.keyRequest.requestType, - gKeyRequestTypes.kKeyRequestTypeRelease); - break; - default: - throwDrmStateException(env, "MediaPlayer/DRM plugin failure: unknown " - "key request type", ERROR_DRM_UNKNOWN); - break; + "Can not release DRM in an active player state."); } } - - return keyObj; -} - -static jbyteArray android_media_MediaPlayer_provideKeyResponse(JNIEnv *env, jobject thiz, - jbyteArray jreleaseKeySetId, jbyteArray jresponse) -{ - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - if (jresponse == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", "key response is null"); - return NULL; - } - - Vector<uint8_t> releaseKeySetId; - if (jreleaseKeySetId != NULL) { - releaseKeySetId = JByteArrayToVector(env, jreleaseKeySetId); - } - - Vector<uint8_t> response(JByteArrayToVector(env, jresponse)); - Vector<uint8_t> keySetId; - - status_t err = mp->provideKeyResponse(releaseKeySetId, response, keySetId); - - if (throwDrmExceptionAsNecessary(env, err, "Failed to handle key response")) { - return NULL; - } - return VectorToJByteArray(env, keySetId); -} - -static void android_media_MediaPlayer_restoreKeys(JNIEnv *env, jobject thiz, jbyteArray jkeySetId) -{ - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return; - } - - if (jkeySetId == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType"); - return; - } - - Vector<uint8_t> keySetId; - keySetId = JByteArrayToVector(env, jkeySetId); - - status_t err = mp->restoreKeys(keySetId); - - ALOGV("JNI restoreKeys err %d ", err); - throwDrmExceptionAsNecessary(env, err, "Failed to restore keys"); -} - -static jstring android_media_MediaPlayer_getDrmPropertyString(JNIEnv *env, jobject thiz, - jstring jname) -{ - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - if (jname == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "property name String is null"); - return NULL; - } - - String8 name = JStringToString8(env, jname); - String8 value; - - status_t err = mp->getDrmPropertyString(name, value); - - ALOGV("JNI getPropertyString err %d", err); - - if (throwDrmExceptionAsNecessary(env, err, "Failed to get property")) { - return NULL; - } - - return env->NewStringUTF(value.string()); -} - -static void android_media_MediaPlayer_setDrmPropertyString(JNIEnv *env, jobject thiz, - jstring jname, jstring jvalue) -{ - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return; - } - - if (jname == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "property name String is null"); - return; - } - - if (jvalue == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "property value String is null"); - return; - } - - String8 name = JStringToString8(env, jname); - String8 value = JStringToString8(env, jvalue); - - status_t err = mp->setDrmPropertyString(name, value); - - ALOGV("JNI setPropertyString err %d", err); - throwDrmExceptionAsNecessary(env, err, "Failed to set property"); } // Modular DRM end // ---------------------------------------------------------------------------- @@ -1802,14 +1431,8 @@ static const JNINativeMethod gMethods[] = { "(I)Landroid/media/VolumeShaper$State;", (void *)android_media_MediaPlayer_getVolumeShaperState}, // Modular DRM - { "_prepareDrm", "([BI)V", (void *)android_media_MediaPlayer_prepareDrm }, + { "_prepareDrm", "([B[B)V", (void *)android_media_MediaPlayer_prepareDrm }, { "_releaseDrm", "()V", (void *)android_media_MediaPlayer_releaseDrm }, - { "_getKeyRequest", "([BLjava/lang/String;ILjava/util/Map;)" "Landroid/media/MediaDrm$KeyRequest;", - (void *)android_media_MediaPlayer_getKeyRequest }, - { "_provideKeyResponse", "([B[B)[B", (void *)android_media_MediaPlayer_provideKeyResponse }, - { "_getDrmPropertyString", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_MediaPlayer_getDrmPropertyString }, - { "_setDrmPropertyString", "(Ljava/lang/String;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_setDrmPropertyString }, - { "_restoreKeys", "([B)V", (void *)android_media_MediaPlayer_restoreKeys }, }; // This function only registers the native methods diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java index acee5ddd5f8f..3831cf7285da 100644 --- a/obex/javax/obex/ServerSession.java +++ b/obex/javax/obex/ServerSession.java @@ -658,6 +658,11 @@ public final class ServerSession extends ObexSession implements Runnable { */ byte[] sendData = new byte[totalLength]; int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport); + if (maxRxLength > mMaxPacketLength) { + if(V) Log.v(TAG,"Set maxRxLength to min of maxRxServrLen:" + maxRxLength + + " and MaxNegotiated from Client: " + mMaxPacketLength); + maxRxLength = mMaxPacketLength; + } sendData[0] = (byte)code; sendData[1] = length[2]; sendData[2] = length[3]; diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java index 8a970da474e6..25127efad502 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -33,7 +33,6 @@ import android.view.View; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; public class DeviceChooserActivity extends Activity { @@ -129,12 +128,9 @@ public class DeviceChooserActivity extends Activity { } protected void onPairTapped(BluetoothDevice selectedDevice) { + getService().onDeviceSelected(); setResult(RESULT_OK, new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice)); finish(); } - - private void toast(String msg) { - Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); - } }
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index ccbee2ae196e..11c722d7676c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -32,16 +32,15 @@ import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.companion.AssociationRequest; -import android.companion.BluetoothDeviceFilterUtils; import android.companion.BluetoothLEDeviceFilter; -import android.companion.ICompanionDeviceManagerService; -import android.companion.IOnAssociateCallback; +import android.companion.ICompanionDeviceDiscoveryService; +import android.companion.ICompanionDeviceDiscoveryServiceCallback; +import android.companion.IFindDeviceCallback; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Color; -import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.IBinder; import android.os.RemoteException; @@ -70,21 +69,25 @@ public class DeviceDiscoveryService extends Service { List<BluetoothDevice> mDevicesFound; BluetoothDevice mSelectedDevice; DevicesAdapter mDevicesAdapter; - IOnAssociateCallback mCallback; + IFindDeviceCallback mFindCallback; + ICompanionDeviceDiscoveryServiceCallback mServiceCallback; String mCallingPackage; - private final ICompanionDeviceManagerService mBinder = - new ICompanionDeviceManagerService.Stub() { + private final ICompanionDeviceDiscoveryService mBinder = + new ICompanionDeviceDiscoveryService.Stub() { @Override public void startDiscovery(AssociationRequest request, - IOnAssociateCallback callback, - String callingPackage) throws RemoteException { + String callingPackage, + IFindDeviceCallback findCallback, + ICompanionDeviceDiscoveryServiceCallback serviceCallback) { if (DEBUG) { Log.i(LOG_TAG, - "startDiscovery() called with: filter = [" + request + "], callback = [" - + callback + "]"); + "startDiscovery() called with: filter = [" + request + + "], findCallback = [" + findCallback + "]" + + "], serviceCallback = [" + serviceCallback + "]"); } - mCallback = callback; + mFindCallback = findCallback; + mServiceCallback = serviceCallback; mCallingPackage = callingPackage; DeviceDiscoveryService.this.startDiscovery(request); } @@ -171,6 +174,14 @@ public class DeviceDiscoveryService extends Service { return super.onUnbind(intent); } + public void onDeviceSelected() { + try { + mServiceCallback.onDeviceSelected(mCallingPackage, getUserId()); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Error reporting selected device"); + } + } + private void stopScan() { if (DEBUG) Log.i(LOG_TAG, "stopScan() called"); mBluetoothAdapter.cancelDiscovery(); @@ -205,7 +216,7 @@ public class DeviceDiscoveryService extends Service { //TODO also, on timeout -> call onFailure private void onReadyToShowUI() { try { - mCallback.onSuccess(PendingIntent.getActivity( + mFindCallback.onSuccess(PendingIntent.getActivity( this, 0, new Intent(this, DeviceChooserActivity.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 11e2a71a0df0..865352320800 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -215,6 +215,13 @@ public class Utils { return colorAccent; } + public static Drawable getDrawable(Context context, int attr) { + TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + Drawable drawable = ta.getDrawable(0); + ta.recycle(); + return drawable; + } + /** * Determine whether a package is a "system package", in which case certain things (like * disabling notifications or disabling the package altogether) should be disallowed. diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 0ec16ae26843..9ac4d2de6c59 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -510,7 +510,7 @@ public class WifiTracker { } NetworkKey key = NetworkKey.createFromScanResult(result); - if (!mRequestedScores.contains(key)) { + if (key != null && !mRequestedScores.contains(key)) { scoresToRequest.add(key); } diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 499b6ae0f5d3..136f17e5a77c 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -227,7 +227,4 @@ <!-- default setting for Settings.System.END_BUTTON_BEHAVIOR : END_BUTTON_BEHAVIOR_SLEEP --> <integer name="def_end_button_behavior">0x2</integer> - - <!--Default settings for network recommendations. --> - <string name="def_network_recommendations_package" translatable="false">com.android.networkrecommendation</string> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index edcb9b51fceb..d5787e6482b5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3182,24 +3182,7 @@ public class SettingsProvider extends ContentProvider { } if (currentVersion == 138) { - // Version 139: Applying the default to NETWORK_RECOMMENDATIONS_PACKAGE - if (userId == UserHandle.USER_SYSTEM) { - final SettingsState globalSettings = getGlobalSettingsLocked(); - final String defaultAppPackage = getContext().getResources() - .getString(R.string.def_network_recommendations_package); - - // Set the network recommendations package name - globalSettings.insertSettingLocked( - Global.NETWORK_RECOMMENDATIONS_PACKAGE, - defaultAppPackage, null, true, - SettingsState.SYSTEM_PACKAGE_NAME); - - // Clear the scorer setting since it's no longer needed. - globalSettings.insertSettingLocked( - Global.NETWORK_SCORER_APP, - null, null, true, - SettingsState.SYSTEM_PACKAGE_NAME); - } + // Version 139: Removed. currentVersion = 139; } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d4c7c7ad8cb1..2e115abaf67d 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -154,8 +154,8 @@ android:name=".BugreportReceiver" android:permission="android.permission.DUMP"> <intent-filter> - <action android:name="android.intent.action.BUGREPORT_STARTED" /> - <action android:name="android.intent.action.BUGREPORT_FINISHED" /> + <action android:name="com.android.internal.intent.action.BUGREPORT_STARTED" /> + <action android:name="com.android.internal.intent.action.BUGREPORT_FINISHED" /> </intent-filter> </receiver> @@ -163,7 +163,7 @@ android:name=".RemoteBugreportReceiver" android:permission="android.permission.DUMP"> <intent-filter> - <action android:name="android.intent.action.REMOTE_BUGREPORT_FINISHED" /> + <action android:name="com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED" /> </intent-filter> </receiver> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 17d0a09d8154..12d0c0306e61 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -133,10 +133,12 @@ public class BugreportProgressService extends Service { private static final String AUTHORITY = "com.android.shell"; // External intents sent by dumpstate. - static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED"; - static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED"; + static final String INTENT_BUGREPORT_STARTED = + "com.android.internal.intent.action.BUGREPORT_STARTED"; + static final String INTENT_BUGREPORT_FINISHED = + "com.android.internal.intent.action.BUGREPORT_FINISHED"; static final String INTENT_REMOTE_BUGREPORT_FINISHED = - "android.intent.action.REMOTE_BUGREPORT_FINISHED"; + "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED"; // Internal intents used on notification actions. static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL"; diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java index 13fc76c5da49..79a0c3599078 100644 --- a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java +++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java @@ -24,7 +24,9 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import com.android.systemui.plugins.OverlayPlugin; +import com.android.systemui.plugins.annotations.Requires; +@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION) public class SampleOverlayPlugin implements OverlayPlugin { private static final String TAG = "SampleOverlayPlugin"; private Context mPluginContext; @@ -36,12 +38,6 @@ public class SampleOverlayPlugin implements OverlayPlugin { private float mStatusBarHeight; @Override - public int getVersion() { - Log.d(TAG, "getVersion " + VERSION); - return VERSION; - } - - @Override public void onCreate(Context sysuiContext, Context pluginContext) { Log.d(TAG, "onCreate"); mPluginContext = pluginContext; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java index 9c173bd16967..97dbafd65d33 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java @@ -14,6 +14,8 @@ package com.android.systemui.plugins; +import com.android.systemui.plugins.annotations.ProvidesInterface; + import android.content.Intent; import android.graphics.drawable.Drawable; @@ -21,6 +23,7 @@ import android.graphics.drawable.Drawable; * An Intent Button represents a triggerable element in SysUI that consists of an * Icon and an intent to trigger when it is activated (clicked, swiped, etc.). */ +@ProvidesInterface(version = IntentButtonProvider.VERSION) public interface IntentButtonProvider extends Plugin { public static final int VERSION = 1; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java index f5074f75b7a4..61aa60bb9675 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java @@ -13,12 +13,15 @@ */ package com.android.systemui.plugins; +import com.android.systemui.plugins.annotations.ProvidesInterface; + import android.view.View; +@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION) public interface OverlayPlugin extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY"; - int VERSION = 1; + int VERSION = 2; void setup(View statusBar, View navBar); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java index e75ecb7a127b..bb93367c3791 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java @@ -13,6 +13,8 @@ */ package com.android.systemui.plugins; +import com.android.systemui.plugins.annotations.Requires; + import android.content.Context; /** @@ -111,18 +113,13 @@ import android.content.Context; public interface Plugin { /** - * Should be implemented as the following directly referencing the version constant - * from the plugin interface being implemented, this will allow recompiles to automatically - * pick up the current version. - * <pre class="prettyprint"> - * {@literal - * public int getVersion() { - * return VERSION; - * } - * } - * @return + * @deprecated + * @see Requires */ - int getVersion(); + default int getVersion() { + // Default of -1 indicates the plugin supports the new Requires model. + return -1; + } default void onCreate(Context sysuiContext, Context pluginContext) { } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java new file mode 100644 index 000000000000..dbbf047519bb --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used for repeated @DependsOn internally, not for plugin + * use. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Dependencies { + DependsOn[] value(); +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java new file mode 100644 index 000000000000..b81d67306307 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins.annotations; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to indicate that an interface in the plugin library needs another + * interface to function properly. When this is added, it will be enforced + * that all plugins that @Requires the annotated interface also @Requires + * the specified class as well. + */ +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(value = Dependencies.class) +public @interface DependsOn { + Class<?> target(); + +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java new file mode 100644 index 000000000000..d0e14b8657ff --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Should be added to all interfaces in plugin lib to specify their + * current version and optionally their action to implement the plugin. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface ProvidesInterface { + int version(); + + String action() default ""; + +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java new file mode 100644 index 000000000000..9cfa279b9c19 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used for repeated @Requires internally, not for plugin + * use. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Requirements { + Requires[] value(); +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java new file mode 100644 index 000000000000..e1b1303b8cb5 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins.annotations; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to annotate which interfaces a given plugin depends on. + * + * At minimum all plugins should have at least one @Requires annotation + * for the plugin interface that they are implementing. They will also + * need an @Requires for each class that the plugin interface @DependsOn. + */ +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(value = Requirements.class) +public @interface Requires { + Class<?> target(); + int version(); +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java index 688df466d244..068848120103 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java @@ -20,10 +20,12 @@ import android.app.PendingIntent; import android.content.Context; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; /** * Provides a {@link DozeUi}. */ +@ProvidesInterface(action = DozeProvider.ACTION, version = DozeProvider.VERSION) public interface DozeProvider extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_DOZE"; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index e21a282581a4..b7467eba1b9c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -14,29 +14,32 @@ package com.android.systemui.plugins.qs; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.PendingIntent; +import com.android.systemui.plugins.FragmentBase; +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.qs.QS.Callback; +import com.android.systemui.plugins.qs.QS.DetailAdapter; +import com.android.systemui.plugins.qs.QS.HeightListener; + import android.content.Context; import android.content.Intent; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.RelativeLayout; -import com.android.systemui.plugins.FragmentBase; - /** * Fragment that contains QS in the notification shade. Most of the interface is for * handling the expand/collapsing of the view interaction. */ +@ProvidesInterface(action = QS.ACTION, version = QS.VERSION) +@DependsOn(target = HeightListener.class) +@DependsOn(target = Callback.class) +@DependsOn(target = DetailAdapter.class) public interface QS extends FragmentBase { public static final String ACTION = "com.android.systemui.action.PLUGIN_QS"; - // This should be incremented any time this class or ActivityStarter or BaseStatusBarHeader - // change in incompatible ways. public static final int VERSION = 5; String TAG = "QS"; @@ -64,17 +67,23 @@ public interface QS extends FragmentBase { public abstract void setContainer(ViewGroup container); + @ProvidesInterface(version = HeightListener.VERSION) public interface HeightListener { + public static final int VERSION = 1; void onQsHeightChanged(); } + @ProvidesInterface(version = Callback.VERSION) public interface Callback { + public static final int VERSION = 1; void onShowingDetail(DetailAdapter detail, int x, int y); void onToggleStateChanged(boolean state); void onScanStateChanged(boolean state); } + @ProvidesInterface(version = DetailAdapter.VERSION) public interface DetailAdapter { + public static final int VERSION = 1; CharSequence getTitle(); Boolean getToggleState(); default boolean getToggleEnabled() { @@ -92,7 +101,9 @@ public interface QS extends FragmentBase { default boolean hasHeader() { return true; } } + @ProvidesInterface(version = BaseStatusBarHeader.VERSION) public abstract static class BaseStatusBarHeader extends RelativeLayout { + public static final int VERSION = 1; public BaseStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java index 41a0907c3228..bc98c8ec388f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java @@ -10,7 +10,10 @@ import android.view.View; import java.util.ArrayList; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; +@ProvidesInterface(action = NotificationMenuRowProvider.ACTION, + version = NotificationMenuRowProvider.VERSION) public interface NotificationMenuRowProvider extends Plugin { public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW"; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java index d54e33fc4dff..5243228121c6 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java @@ -14,14 +14,15 @@ package com.android.systemui.plugins.statusbar.phone; -import android.annotation.DrawableRes; import android.annotation.Nullable; import android.graphics.drawable.Drawable; import android.view.View; import android.view.ViewGroup; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; +@ProvidesInterface(action = NavBarButtonProvider.ACTION, version = NavBarButtonProvider.VERSION) public interface NavBarButtonProvider extends Plugin { public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_BUTTON"; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java index 918d6e96784d..ddee89ed74ec 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java @@ -17,7 +17,9 @@ package com.android.systemui.plugins.statusbar.phone; import android.view.MotionEvent; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; +@ProvidesInterface(action = NavGesture.ACTION, version = NavBarButtonProvider.VERSION) public interface NavGesture extends Plugin { public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_GESTURE"; diff --git a/packages/SystemUI/res/drawable/ic_data_saver.xml b/packages/SystemUI/res/drawable/ic_data_saver.xml index 9c3bd3a7f374..64bbff02cbbc 100644 --- a/packages/SystemUI/res/drawable/ic_data_saver.xml +++ b/packages/SystemUI/res/drawable/ic_data_saver.xml @@ -23,7 +23,7 @@ android:fillColor="#FFFFFFFF" android:pathData="M12.0,19.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.5 2.6,-6.4 6.0,-6.9L11.0,2.0C5.9,2.5 2.0,6.8 2.0,12.0c0.0,5.5 4.5,10.0 10.0,10.0c3.3,0.0 6.2,-1.6 8.1,-4.1l-2.6,-1.5C16.2,18.0 14.2,19.0 12.0,19.0z"/> <path - android:fillColor="#4DFFFFFF" + android:fillColor="#54FFFFFF" android:pathData="M13.0,2.0l0.0,3.0c3.4,0.5 6.0,3.4 6.0,6.9c0.0,0.9 -0.2,1.8 -0.5,2.5l2.6,1.5c0.6,-1.2 0.9,-2.6 0.9,-4.1C22.0,6.8 18.0,2.6 13.0,2.0z"/> <path android:fillColor="#FFFFFFFF" diff --git a/packages/SystemUI/res/drawable/ic_data_saver_off.xml b/packages/SystemUI/res/drawable/ic_data_saver_off.xml index 918c61c3fc39..3001ad9fa7be 100644 --- a/packages/SystemUI/res/drawable/ic_data_saver_off.xml +++ b/packages/SystemUI/res/drawable/ic_data_saver_off.xml @@ -20,9 +20,9 @@ android:viewportHeight="24.0" android:tint="?android:attr/colorControlNormal"> <path - android:fillColor="#4DFFFFFF" + android:fillColor="#FFFFFFFF" android:pathData="M12.0,19.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.5 2.6,-6.4 6.0,-6.9L11.0,2.0C5.9,2.5 2.0,6.8 2.0,12.0c0.0,5.5 4.5,10.0 10.0,10.0c3.3,0.0 6.2,-1.6 8.1,-4.1l-2.6,-1.5C16.2,18.0 14.2,19.0 12.0,19.0z"/> <path - android:fillColor="#4DFFFFFF" + android:fillColor="#FFFFFFFF" android:pathData="M13.0,2.0l0.0,3.0c3.4,0.5 6.0,3.4 6.0,6.9c0.0,0.9 -0.2,1.8 -0.5,2.5l2.6,1.5c0.6,-1.2 0.9,-2.6 0.9,-4.1C22.0,6.8 18.0,2.6 13.0,2.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_remove_circle.xml b/packages/SystemUI/res/drawable/ic_remove_circle.xml new file mode 100644 index 000000000000..439cc7896cc8 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_remove_circle.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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:height="48dp" + android:width="48dp" + android:tint="#db4437" + android:viewportHeight="48" + android:viewportWidth="48" > + <path android:fillColor="@android:color/white" + android:pathData="M24,4C12.95,4,4,12.95,4,24 + s8.95,20,20,20,20-8.95,20-20 + S35.05,4,24,4zm10,22H14v-4h20v4z"/> +</vector> diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml index d6abc47c5af3..acae9f51c142 100644 --- a/packages/SystemUI/res/layout/battery_percentage_view.xml +++ b/packages/SystemUI/res/layout/battery_percentage_view.xml @@ -25,5 +25,5 @@ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" android:textColor="?android:attr/textColorPrimary" android:gravity="center_vertical|start" - android:paddingEnd="4dp" + android:paddingStart="4dp" /> diff --git a/packages/SystemUI/res/layout/divider.xml b/packages/SystemUI/res/layout/divider.xml index 95814371ed56..f1f0df054240 100644 --- a/packages/SystemUI/res/layout/divider.xml +++ b/packages/SystemUI/res/layout/divider.xml @@ -16,5 +16,6 @@ <View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="72dp" android:layout_height="1dp" + android:layout_marginTop="8dp" android:background="?android:attr/colorForeground" android:alpha="?android:attr/disabledAlpha" /> diff --git a/packages/SystemUI/res/layout/preference_widget_radiobutton.xml b/packages/SystemUI/res/layout/preference_widget_radiobutton.xml new file mode 100644 index 000000000000..b3ec43da3eda --- /dev/null +++ b/packages/SystemUI/res/layout/preference_widget_radiobutton.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2006 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. +--> + +<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated + inside android.R.layout.preference. --> +<RadioButton xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:focusable="false" + android:clickable="false" /> diff --git a/packages/SystemUI/res/layout/qs_page_indicator.xml b/packages/SystemUI/res/layout/qs_page_indicator.xml index 02bd31a4c972..583753a362f1 100644 --- a/packages/SystemUI/res/layout/qs_page_indicator.xml +++ b/packages/SystemUI/res/layout/qs_page_indicator.xml @@ -20,9 +20,8 @@ android:layout_width="match_parent" android:layout_height="48dp" android:layout_gravity="center" - android:layout_marginTop="40dp" android:layout_marginBottom="24dp" android:focusable="true" android:gravity="center" android:importantForAccessibility="yes" - android:visibility="gone"/>
\ No newline at end of file + android:visibility="gone"/> diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml index 8ff1d1e3ba18..00427cb1c428 100644 --- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml +++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml @@ -19,6 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingBottom="24dp" android:clipChildren="false" android:clipToPadding="false"> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index a093b8734dbb..8d1f9e4131f9 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -16,10 +16,12 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingTop="16dp"> + android:gravity="center_horizontal" + android:paddingTop="8dp" + android:paddingBottom="8dp"> <TextView android:id="@+id/tile_label" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml index 6988c765cf96..080f553e123e 100644 --- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml +++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml @@ -17,7 +17,6 @@ xmlns:systemui="http://schemas.android.com/apk/res-auto" android:layout_height="48dp" android:layout_width="match_parent" - android:layout_marginBottom="24dp" android:paddingLeft="16dp" android:paddingRight="16dp" style="@style/BrightnessDialogContainer"> diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index 3e8e72a3d100..78d4bdd6c9dc 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -85,16 +85,6 @@ <include layout="@layout/system_icons" /> </FrameLayout> - - <TextView - android:id="@+id/battery_level" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginStart="@dimen/header_battery_margin_expanded" - android:importantForAccessibility="noHideDescendants" - android:textColor="?android:attr/textColorPrimary" - android:textSize="@dimen/battery_level_text_size"/> </LinearLayout> <com.android.systemui.statusbar.AlphaOptimizedFrameLayout diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2102e0712015..40d4d6f1a843 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -219,8 +219,8 @@ <!-- The size of the gesture span needed to activate the "pull" notification expansion --> <dimen name="pull_span_min">25dp</dimen> - <dimen name="qs_tile_height">80dp</dimen> - <dimen name="qs_tile_margin">36dp</dimen> + <dimen name="qs_tile_height">88dp</dimen> + <dimen name="qs_tile_margin">28dp</dimen> <dimen name="qs_tile_margin_top">16dp</dimen> <dimen name="qs_quick_tile_size">48dp</dimen> <dimen name="qs_quick_tile_padding">12dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 67def4fa8ee0..b825cfb5f903 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1527,14 +1527,12 @@ <!-- SysUI Tuner: Button that controls layout of navigation bar [CHAR LIMIT=60] --> <string name="nav_bar_layout">Layout</string> - <!-- SysUI Tuner: Label for section of settings about the left nav button [CHAR LIMIT=60] --> - <string name="nav_bar_left">Left</string> - - <!-- SysUI Tuner: Label for section of settings about the right nav button [CHAR LIMIT=60] --> - <string name="nav_bar_right">Right</string> + <!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] --> + <string name="left_nav_bar_button_type">Extra left button type</string> <!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] --> - <string name="nav_bar_button_type">Button type</string> + <string name="right_nav_bar_button_type">Extra right button type</string> + <!-- SysUI Tuner: Added to nav bar option to indicate it is the default [CHAR LIMIT=60] --> <string name="nav_bar_default"> (default)</string> @@ -1543,7 +1541,7 @@ <string-array name="nav_bar_buttons"> <item>Clipboard</item> <item>Keycode</item> - <item>Menu / Keyboard Switcher</item> + <item>Keyboard switcher</item> <item>None</item> </string-array> <string-array name="nav_bar_button_values" translatable="false"> @@ -1555,10 +1553,10 @@ <!-- SysUI Tuner: Labels for different types of navigation bar layouts [CHAR LIMIT=60] --> <string-array name="nav_bar_layouts"> - <item>Divided (default)</item> - <item>Centered</item> - <item>Left-aligned</item> - <item>Right-aligned</item> + <item>Normal</item> + <item>Compact</item> + <item>Left-leaning</item> + <item>Right-leaning</item> </string-array> <string-array name="nav_bar_layouts_values" translatable="false"> @@ -1569,7 +1567,7 @@ </string-array> <!-- SysUI Tuner: Name of Combination Menu / Keyboard Switcher button [CHAR LIMIT=30] --> - <string name="menu_ime">Menu / Keyboard Switcher</string> + <string name="menu_ime">Keyboard switcher</string> <!-- SysUI Tuner: Save the current settings [CHAR LIMIT=30] --> <string name="save">Save</string> <!-- SysUI Tuner: Reset to default settings [CHAR LIMIT=30] --> @@ -1585,10 +1583,16 @@ <string name="accessibility_key">Custom navigation button</string> <!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] --> - <string name="keycode">Keycode</string> + <string name="left_keycode">Left keycode</string> + + <!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] --> + <string name="right_keycode">Right keycode</string> + + <!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] --> + <string name="left_icon">Left icon</string> <!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] --> - <string name="icon">Icon</string> + <string name="right_icon">Right icon</string> <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] --> <string name="drag_to_add_tiles">Drag to add tiles</string> @@ -1721,10 +1725,17 @@ not appear on production builds ever. --> <string name="tuner_doze" translatable="false">Ambient Display</string> + <!-- Ambient display, Sensors wake up device of the tuner. Non-translatable since it should + not appear on production builds ever. --> + <string name="tuner_doze_sensors_wake_up_fully" translatable="false">Wake up device on double tap or lift</string> + <!-- Ambient display always-on of the tuner. Non-translatable since it should not appear on production builds ever. --> <string name="tuner_doze_always_on" translatable="false">Always on</string> + <!-- SysUI Tuner: Section to customize lockscreen shortcuts [CHAR LIMIT=60] --> + <string name="tuner_lock_screen">Lock screen</string> + <!-- Making the PIP fullscreen [CHAR LIMIT=25] --> <string name="pip_phone_expand">Expand</string> @@ -1734,18 +1745,6 @@ <!-- Label for PIP the drag to close zone [CHAR LIMIT=NONE]--> <string name="pip_phone_close">Close</string> - <!-- PIP section of the tuner. Non-translatable since it should - not appear on production builds ever. --> - <string name="picture_in_picture" translatable="false">Picture-in-Picture</string> - - <!-- PIP drag to dismiss title. Non-translatable since it should - not appear on production builds ever. --> - <string name="pip_drag_to_dismiss_title" translatable="false">Drag to dismiss</string> - - <!-- PIP drag to dismiss description. Non-translatable since it should - not appear on production builds ever. --> - <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string> - <!-- Tuner string --> <string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string> <!-- Tuner string --> @@ -1760,20 +1759,47 @@ <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] --> <string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string> - <!-- SysUI Tuner: Group of settings for left lock screen affordance [CHAR LIMIT=60] --> - <string name="lockscreen_left">Left</string> + <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] --> + <string name="lockscreen_shortcut_left">Left shortcut</string> - <!-- SysUI Tuner: Group of settings for right lock screen affordance [CHAR LIMIT=60] --> - <string name="lockscreen_right">Right</string> + <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] --> + <string name="lockscreen_shortcut_right">Right shortcut</string> - <!-- SysUI Tuner: Switch controlling whether to customize lock screen button [CHAR LIMIT=60] --> - <string name="lockscreen_customize">Customize shortcut</string> + <!-- SysUI Tuner: Switch to control if device gets unlocked by left shortcut [CHAR LIMIT=60] --> + <string name="lockscreen_unlock_left">Left shortcut also unlocks</string> - <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] --> - <string name="lockscreen_shortcut">Shortcut</string> + <!-- SysUI Tuner: Switch to control if device gets unlocked by right shortcut [CHAR LIMIT=60] --> + <string name="lockscreen_unlock_right">Right shortcut also unlocks</string> + + <!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] --> + <string name="lockscreen_none">None</string> + + <!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] --> + <string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string> + + <!-- SysUI Tuner: Label for section of other apps that can be launched [CHAR LIMIT=60] --> + <string name="tuner_other_apps">Other apps</string> + + <!-- SysUI Tuner: Label for icon shaped like a circle [CHAR LIMIT=60] --> + <string name="tuner_circle">Circle</string> + + <!-- SysUI Tuner: Label for icon shaped like a plus [CHAR LIMIT=60] --> + <string name="tuner_plus">Plus</string> + + <!-- SysUI Tuner: Label for icon shaped like a minus [CHAR LIMIT=60] --> + <string name="tuner_minus">Minus</string> + + <!-- SysUI Tuner: Label for icon shaped like a left [CHAR LIMIT=60] --> + <string name="tuner_left">Left</string> + + <!-- SysUI Tuner: Label for icon shaped like a right [CHAR LIMIT=60] --> + <string name="tuner_right">Right</string> + + <!-- SysUI Tuner: Label for icon shaped like a menu [CHAR LIMIT=60] --> + <string name="tuner_menu">Menu</string> - <!-- SysUI Tuner: Switch to control if device gets unlocked [CHAR LIMIT=60] --> - <string name="lockscreen_unlock">Prompt for password</string> + <!-- SysUI Tuner: App subheading for shortcut selection [CHAR LIMIT=60] --> + <string name="tuner_app"><xliff:g id="app">%1$s</xliff:g> app</string> <!-- Title for the notification channel containing important alerts like low battery. [CHAR LIMIT=NONE] --> <string name="notification_channel_alerts">Alerts</string> diff --git a/packages/SystemUI/res/xml/lockscreen_settings.xml b/packages/SystemUI/res/xml/lockscreen_settings.xml index 73e29af42821..1e7d266cc5cc 100644 --- a/packages/SystemUI/res/xml/lockscreen_settings.xml +++ b/packages/SystemUI/res/xml/lockscreen_settings.xml @@ -18,42 +18,25 @@ xmlns:sysui="http://schemas.android.com/apk/res-auto" android:title="@string/other"> - <PreferenceCategory - android:key="left" - android:title="@string/lockscreen_left"> - <SwitchPreference - android:key="customize" - android:title="@string/lockscreen_customize" /> - - <Preference - android:key="shortcut" - android:title="@string/lockscreen_shortcut" /> - - <com.android.systemui.tuner.TunerSwitch - android:key="sysui_keyguard_left_unlock" - android:title="@string/lockscreen_unlock" - sysui:defValue="true" /> - - </PreferenceCategory> - - <PreferenceCategory - android:key="right" - android:title="@string/lockscreen_right"> - - <SwitchPreference - android:key="customize" - android:title="@string/lockscreen_customize" /> - - <Preference - android:key="shortcut" - android:title="@string/lockscreen_shortcut" /> - - <com.android.systemui.tuner.TunerSwitch - android:key="sysui_keyguard_right_unlock" - android:title="@string/lockscreen_unlock" - sysui:defValue="true" /> - - </PreferenceCategory> + <Preference + android:key="sysui_keyguard_left" + android:title="@string/lockscreen_shortcut_left" + android:fragment="com.android.systemui.tuner.ShortcutPicker" /> + + <com.android.systemui.tuner.TunerSwitch + android:key="sysui_keyguard_left_unlock" + android:title="@string/lockscreen_unlock_left" + sysui:defValue="true" /> + + <Preference + android:key="sysui_keyguard_right" + android:title="@string/lockscreen_shortcut_right" + android:fragment="com.android.systemui.tuner.ShortcutPicker" /> + + <com.android.systemui.tuner.TunerSwitch + android:key="sysui_keyguard_right_unlock" + android:title="@string/lockscreen_unlock_right" + sysui:defValue="true" /> </PreferenceScreen> diff --git a/packages/SystemUI/res/xml/nav_bar_tuner.xml b/packages/SystemUI/res/xml/nav_bar_tuner.xml index 6fa8bec3a937..68e8fad1e24a 100644 --- a/packages/SystemUI/res/xml/nav_bar_tuner.xml +++ b/packages/SystemUI/res/xml/nav_bar_tuner.xml @@ -18,7 +18,7 @@ xmlns:sysui="http://schemas.android.com/apk/res-auto" android:title="@string/nav_bar"> - <ListPreference + <com.android.systemui.tuner.RadioListPreference android:key="layout" android:title="@string/nav_bar_layout" android:summary="%s" @@ -26,54 +26,42 @@ android:entries="@array/nav_bar_layouts" android:entryValues="@array/nav_bar_layouts_values" /> - <PreferenceCategory - android:key="left" - android:title="@string/nav_bar_left"> - - <DropDownPreference - android:key="type_left" - android:title="@string/nav_bar_button_type" - android:persistent="false" - android:summary="%s" - android:entries="@array/nav_bar_buttons" - android:entryValues="@array/nav_bar_button_values" /> - - <Preference - android:key="keycode_left" - android:persistent="false" - android:title="@string/keycode" /> - - <com.android.systemui.tuner.BetterListPreference - android:key="icon_left" - android:persistent="false" - android:summary="%s" - android:title="@string/icon" /> - - </PreferenceCategory> + <com.android.systemui.tuner.RadioListPreference + android:key="type_left" + android:title="@string/left_nav_bar_button_type" + android:persistent="false" + android:summary="%s" + android:entries="@array/nav_bar_buttons" + android:entryValues="@array/nav_bar_button_values" /> - <PreferenceCategory - android:key="right" - android:title="@string/nav_bar_right"> + <Preference + android:key="keycode_left" + android:persistent="false" + android:title="@string/left_keycode" /> - <DropDownPreference - android:key="type_right" - android:title="@string/nav_bar_button_type" - android:summary="%s" - android:persistent="false" - android:entries="@array/nav_bar_buttons" - android:entryValues="@array/nav_bar_button_values" /> + <com.android.systemui.tuner.RadioListPreference + android:key="icon_left" + android:persistent="false" + android:summary="%s" + android:title="@string/left_icon" /> - <Preference - android:key="keycode_right" - android:persistent="false" - android:title="@string/keycode" /> + <com.android.systemui.tuner.RadioListPreference + android:key="type_right" + android:title="@string/right_nav_bar_button_type" + android:summary="%s" + android:persistent="false" + android:entries="@array/nav_bar_buttons" + android:entryValues="@array/nav_bar_button_values" /> - <com.android.systemui.tuner.BetterListPreference - android:key="icon_right" - android:persistent="false" - android:summary="%s" - android:title="@string/icon" /> + <Preference + android:key="keycode_right" + android:persistent="false" + android:title="@string/right_keycode" /> - </PreferenceCategory> + <com.android.systemui.tuner.RadioListPreference + android:key="icon_right" + android:persistent="false" + android:summary="%s" + android:title="@string/right_icon" /> </PreferenceScreen> diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index 6198ab7a3b73..85f12b5baabb 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -121,18 +121,7 @@ </PreferenceScreen> - <PreferenceScreen - android:key="picture_in_picture" - android:title="@string/picture_in_picture"> - - <com.android.systemui.tuner.TunerSwitch - android:key="pip_drag_to_dismiss" - android:title="@string/pip_drag_to_dismiss_title" - android:summary="@string/pip_drag_to_dismiss_summary" - sysui:defValue="false" /> - - </PreferenceScreen> - + <!-- <PreferenceScreen android:key="doze" android:title="@string/tuner_doze"> @@ -142,7 +131,13 @@ android:title="@string/tuner_doze_always_on" sysui:defValue="false" /> + <com.android.systemui.tuner.TunerSwitch + android:key="doze_sensors_wake_up_fully" + android:title="@string/tuner_doze_sensors_wake_up_fully" + sysui:defValue="false" /> + </PreferenceScreen> + --> <Preference android:key="nav_bar" @@ -151,15 +146,10 @@ <Preference android:key="lockscreen" - android:title="@string/accessibility_desc_lock_screen" + android:title="@string/tuner_lock_screen" android:fragment="com.android.systemui.tuner.LockscreenFragment" /> <Preference - android:key="other" - android:title="@string/other" - android:fragment="com.android.systemui.tuner.OtherPrefs" /> - - <Preference android:key="plugins" android:title="@string/plugins" android:fragment="com.android.systemui.tuner.PluginFragment" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 80d4a264cac8..1f58d4c55d1d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -595,6 +595,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } + public boolean isScreenOn() { + return mScreenOn; + } + static class DisplayClientState { public int clientGeneration; public boolean clearing; diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index f82130868554..bda4c957c5e8 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -65,6 +65,7 @@ public class BatteryMeterView extends LinearLayout implements private SettingObserver mSettingObserver; private int mTextColor; private int mLevel; + private boolean mForceShowPercent; public BatteryMeterView(Context context) { this(context, null, 0); @@ -103,6 +104,11 @@ public class BatteryMeterView extends LinearLayout implements updateShowPercent(); } + public void forceShowPercent() { + mForceShowPercent = true; + updateShowPercent(); + } + // StatusBarIconController reaches in here and adjusts the layout parameters of the icon public ImageView getBatteryIconView() { return mBatteryIconView; @@ -173,13 +179,12 @@ public class BatteryMeterView extends LinearLayout implements private void updateShowPercent() { final boolean showing = mBatteryPercentView != null; if (0 != Settings.System.getInt(getContext().getContentResolver(), - BatteryMeterView.SHOW_PERCENT_SETTING, 0)) { + BatteryMeterView.SHOW_PERCENT_SETTING, 0) || mForceShowPercent) { if (!showing) { mBatteryPercentView = loadPercentView(); if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor); updatePercentText(); addView(mBatteryPercentView, - 0, new ViewGroup.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 273b5e3644a6..f1e7d53b037f 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -25,6 +25,8 @@ import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.systemui.assist.AssistManager; +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentService; import com.android.systemui.plugins.PluginManager; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; @@ -74,6 +76,7 @@ import com.android.systemui.util.leak.LeakReporter; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; +import java.util.function.Consumer; /** * Class to handle ugly dependencies throughout sysui until we determine the @@ -227,6 +230,9 @@ public class Dependency extends SystemUI { mProviders.put(StatusBarIconController.class, () -> new StatusBarIconControllerImpl(mContext)); + mProviders.put(FragmentService.class, () -> + new FragmentService(mContext)); + // Put all dependencies above here so the factory can override them if it wants. SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); } @@ -282,17 +288,42 @@ public class Dependency extends SystemUI { T createDependency(); } + private <T> void destroyDependency(Class<T> cls, Consumer<T> destroy) { + T dep = (T) mDependencies.remove(cls); + if (dep != null && destroy != null) { + destroy.accept(dep); + } + } + /** * Used in separate processes (like tuner settings) to init the dependencies. */ public static void initDependencies(Context context) { if (sDependency != null) return; Dependency d = new Dependency(); - d.mContext = context.getApplicationContext(); + d.mContext = context; d.mComponents = new HashMap<>(); d.start(); } + /** + * Used in separate process teardown to ensure the context isn't leaked. + * + * TODO: Remove once PreferenceFragment doesn't reference getActivity() + * anymore and these context hacks are no longer needed. + */ + public static void clearDependencies() { + sDependency = null; + } + + /** + * Checks to see if a dependency is instantiated, if it is it removes it from + * the cache and calls the destroy callback. + */ + public static <T> void destroy(Class<T> cls, Consumer<T> destroy) { + sDependency.destroyDependency(cls, destroy); + } + public static <T> T get(Class<T> cls) { return sDependency.getDependency(cls); } diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java index 9cc66138cfb4..ddd48330e679 100644 --- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java +++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java @@ -53,8 +53,7 @@ public class PluginInflateContainer extends AutoReinflateContainer private static final String TAG = "PluginInflateContainer"; - private String mAction; - private int mVersion; + private Class<?> mClass; private View mPluginView; public PluginInflateContainer(Context context, @Nullable AttributeSet attrs) { @@ -62,28 +61,25 @@ public class PluginInflateContainer extends AutoReinflateContainer TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer); String viewType = a.getString(R.styleable.PluginInflateContainer_viewType); try { - Class c = Class.forName(viewType); - mAction = (String) c.getDeclaredField("ACTION").get(null); - mVersion = (int) c.getDeclaredField("VERSION").get(null); + mClass = Class.forName(viewType); } catch (Exception e) { Log.d(TAG, "Problem getting class info " + viewType, e); - mAction = null; - mVersion = 0; + mClass = null; } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (mAction != null) { - Dependency.get(PluginManager.class).addPluginListener(mAction, this, mVersion); + if (mClass != null) { + Dependency.get(PluginManager.class).addPluginListener(this, mClass); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mAction != null) { + if (mClass != null) { Dependency.get(PluginManager.class).removePluginListener(this); } } diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 19ae2954bb2a..b9ae585c339c 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -49,6 +49,7 @@ public final class Prefs { Key.QS_WORK_ADDED, }) public @interface Key { + @Deprecated String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime"; String DEBUG_MODE_ENABLED = "debugModeEnabled"; String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed"; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 187b5573b97c..be6986750804 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -36,6 +36,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.media.RingtonePlayer; import com.android.systemui.pip.PipUI; import com.android.systemui.plugins.OverlayPlugin; +import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.power.PowerUI; @@ -67,7 +68,6 @@ public class SystemUIApplication extends Application implements SysUiServiceProv */ private final Class<?>[] SERVICES = new Class[] { Dependency.class, - FragmentService.class, NotificationChannels.class, CommandQueue.CommandQueueStart.class, KeyguardViewMediator.class, @@ -207,7 +207,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv mServices[i].onBootCompleted(); } } - Dependency.get(PluginManager.class).addPluginListener(OverlayPlugin.ACTION, + Dependency.get(PluginManager.class).addPluginListener( new PluginListener<OverlayPlugin>() { private ArraySet<OverlayPlugin> mOverlays; @@ -236,7 +236,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv Dependency.get(StatusBarWindowManager.class).setForcePluginOpen( mOverlays.size() != 0); } - }, OverlayPlugin.VERSION, true /* Allow multiple plugins */); + }, OverlayPlugin.class, true /* Allow multiple plugins */); mServicesStarted = true; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 7c15096a5f9a..b3a0eb7baa72 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -91,8 +91,7 @@ public class DozeFactory { @Override public void requestState(DozeProvider.DozeState state) { if (state == DozeProvider.DozeState.WAKE_UP) { - PowerManager pm = context.getSystemService(PowerManager.class); - pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE"); + machine.wakeUp(); return; } machine.requestState(implState(state)); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 6a868d53d409..c9eb790c695e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -158,6 +158,11 @@ public class DozeMachine { return mState; } + /** Requests the PowerManager to wake up now. */ + public void wakeUp() { + mDozeService.requestWakeUp(); + } + private boolean isExecutingTransition() { return !mQueuedRequests.isEmpty(); } @@ -300,5 +305,8 @@ public class DozeMachine { /** Request a display state. See {@link android.view.Display#STATE_DOZE}. */ void setDozeScreenState(int state); + + /** Request waking up. */ + void requestWakeUp(); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 94dc9a344a34..e55a59722385 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -16,6 +16,8 @@ package com.android.systemui.doze; +import android.os.PowerManager; +import android.os.SystemClock; import android.service.dreams.DreamService; import android.util.Log; @@ -49,7 +51,7 @@ public class DozeService extends DreamService implements DozeMachine.Service { } DozeProvider provider = Dependency.get(PluginManager.class) - .getOneShotPlugin(DozeProvider.ACTION, DozeProvider.VERSION); + .getOneShotPlugin(DozeProvider.class); mDozeMachine = new DozeFactory(provider).assembleMachine(this); } @@ -72,4 +74,10 @@ public class DozeService extends DreamService implements DozeMachine.Service { mDozeMachine.dump(pw); } } + + @Override + public void requestWakeUp() { + PowerManager pm = getSystemService(PowerManager.class); + pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE"); + } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index db5a3921db9d..b5c7dd3bfc66 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -97,7 +97,11 @@ public class DozeTriggers implements DozeMachine.Part { } private void onSensor(int pulseReason, boolean sensorPerformedProxCheck) { - requestPulse(pulseReason, sensorPerformedProxCheck); + if (mDozeParameters.getSensorsWakeUpFully()) { + mMachine.wakeUp(); + } else { + requestPulse(pulseReason, sensorPerformedProxCheck); + } if (pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP) { final long timeSinceNotification = diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 0c6bf52e84ba..57c75bf6f54c 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -32,6 +32,7 @@ import android.view.LayoutInflater; import android.view.View; import com.android.settingslib.applications.InterestingConfigChanges; +import com.android.systemui.Dependency; import com.android.systemui.SystemUIApplication; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginManager; @@ -171,6 +172,10 @@ public class FragmentHostManager { return mPlugins; } + void destroy() { + mFragments.dispatchDestroy(); + } + public interface FragmentListener { void onFragmentViewCreated(String tag, Fragment fragment); @@ -182,8 +187,7 @@ public class FragmentHostManager { public static FragmentHostManager get(View view) { try { - return ((SystemUIApplication) view.getContext().getApplicationContext()) - .getComponent(FragmentService.class).getFragmentHostManager(view); + return Dependency.get(FragmentService.class).getFragmentHostManager(view); } catch (ClassCastException e) { // TODO: Some auto handling here? throw e; diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java index 85cde10b5a53..9a8512daf58c 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java @@ -14,6 +14,7 @@ package com.android.systemui.fragments; +import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; @@ -21,6 +22,7 @@ import android.util.ArrayMap; import android.util.Log; import android.view.View; +import com.android.systemui.ConfigurationChangedReceiver; import com.android.systemui.SystemUI; import com.android.systemui.SystemUIApplication; @@ -28,16 +30,16 @@ import com.android.systemui.SystemUIApplication; * Holds a map of root views to FragmentHostStates and generates them as needed. * Also dispatches the configuration changes to all current FragmentHostStates. */ -public class FragmentService extends SystemUI { +public class FragmentService implements ConfigurationChangedReceiver { private static final String TAG = "FragmentService"; private final ArrayMap<View, FragmentHostState> mHosts = new ArrayMap<>(); private final Handler mHandler = new Handler(); + private final Context mContext; - @Override - public void start() { - putComponent(FragmentService.class, this); + public FragmentService(Context context) { + mContext = context; } public FragmentHostManager getFragmentHostManager(View view) { @@ -50,8 +52,14 @@ public class FragmentService extends SystemUI { return state.getFragmentHostManager(); } + public void destroyAll() { + for (FragmentHostState state : mHosts.values()) { + state.mFragmentHostManager.destroy(); + } + } + @Override - protected void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(Configuration newConfig) { for (FragmentHostState state : mHosts.values()) { state.sendConfigurationChange(newConfig); } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java index 1eaca6ffd9db..03bb73da3902 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java @@ -44,8 +44,9 @@ public class PluginFragmentListener implements PluginListener<Plugin> { mDefaultClass = defaultFragment; } - public void startListening(String action, int version) { - mPluginManager.addPluginListener(action, this, version, false /* Only allow one */); + public void startListening() { + mPluginManager.addPluginListener(this, mExpectedInterface, + false /* Only allow one */); } public void stopListening() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index e8c0050ef6d1..d832810e4812 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -37,9 +37,7 @@ import android.view.ViewConfiguration; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.PipSnapAlgorithm; -import com.android.systemui.Dependency; import com.android.systemui.statusbar.FlingAnimationUtils; -import com.android.systemui.tuner.TunerService; import java.io.PrintWriter; @@ -47,17 +45,18 @@ import java.io.PrintWriter; * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. */ -public class PipTouchHandler implements TunerService.Tunable { +public class PipTouchHandler { private static final String TAG = "PipTouchHandler"; // These values are used for metrics and should never change private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0; private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1; - private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss"; - private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 200; + // Allow dragging the PIP to a location to close it + private static final boolean ENABLE_DRAG_TO_DISMISS = false; + private final Context mContext; private final IActivityManager mActivityManager; private final IWindowManager mWindowManager; @@ -70,9 +69,6 @@ public class PipTouchHandler implements TunerService.Tunable { private final PipDismissViewController mDismissViewController; private final PipSnapAlgorithm mSnapAlgorithm; - // Allow dragging the PIP to a location to close it - private boolean mEnableDragToDismiss = false; - // The current movement bounds private Rect mMovementBounds = new Rect(); @@ -86,7 +82,7 @@ public class PipTouchHandler implements TunerService.Tunable { private Runnable mShowDismissAffordance = new Runnable() { @Override public void run() { - if (mEnableDragToDismiss) { + if (ENABLE_DRAG_TO_DISMISS) { mDismissViewController.showDismissTarget(mMotionHelper.getBounds()); } } @@ -183,23 +179,6 @@ public class PipTouchHandler implements TunerService.Tunable { mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mSnapAlgorithm, mFlingAnimationUtils); registerInputConsumer(); - - // Register any tuner settings changes - Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_DRAG_TO_DISMISS); - } - - @Override - public void onTuningChanged(String key, String newValue) { - if (newValue == null) { - // Reset back to default - mEnableDragToDismiss = false; - return; - } - switch (key) { - case TUNER_KEY_DRAG_TO_DISMISS: - mEnableDragToDismiss = Integer.parseInt(newValue) != 0; - break; - } } public void onActivityPinned() { @@ -439,7 +418,7 @@ public class PipTouchHandler implements TunerService.Tunable { @Override public void onDown(PipTouchState touchState) { - if (mEnableDragToDismiss) { + if (ENABLE_DRAG_TO_DISMISS) { mDismissViewController.createDismissTarget(); mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY); } @@ -451,7 +430,7 @@ public class PipTouchHandler implements TunerService.Tunable { mSavedSnapFraction = -1f; } - if (touchState.startedDragging() && mEnableDragToDismiss) { + if (touchState.startedDragging() && ENABLE_DRAG_TO_DISMISS) { mHandler.removeCallbacks(mShowDismissAffordance); mDismissViewController.showDismissTarget(mMotionHelper.getBounds()); } @@ -469,7 +448,7 @@ public class PipTouchHandler implements TunerService.Tunable { mTmpBounds.offsetTo((int) left, (int) top); mMotionHelper.movePip(mTmpBounds); - if (mEnableDragToDismiss) { + if (ENABLE_DRAG_TO_DISMISS) { mDismissViewController.updateDismissTarget(mTmpBounds); } return true; @@ -480,7 +459,7 @@ public class PipTouchHandler implements TunerService.Tunable { @Override public boolean onUp(PipTouchState touchState) { try { - if (mEnableDragToDismiss) { + if (ENABLE_DRAG_TO_DISMISS) { mHandler.removeCallbacks(mShowDismissAffordance); PointF vel = mTouchState.getVelocity(); final float velocity = PointF.length(vel.x, vel.y); @@ -583,7 +562,7 @@ public class PipTouchHandler implements TunerService.Tunable { pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); pw.println(innerPrefix + "mImeHeight=" + mImeHeight); pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction); - pw.println(innerPrefix + "mEnableDragToDismiss=" + mEnableDragToDismiss); + pw.println(innerPrefix + "mEnableDragToDismiss=" + ENABLE_DRAG_TO_DISMISS); mSnapAlgorithm.dump(pw, innerPrefix); mTouchState.dump(pw, innerPrefix); mMotionHelper.dump(pw, innerPrefix); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java index dd1614b50ca6..e895fa226cf3 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java @@ -18,12 +18,10 @@ import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -40,6 +38,7 @@ import android.view.LayoutInflater; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.plugins.VersionInfo.InvalidVersionException; import java.util.ArrayList; import java.util.List; @@ -55,7 +54,7 @@ public class PluginInstanceManager<T extends Plugin> { private final PluginListener<T> mListener; private final String mAction; private final boolean mAllowMultiple; - private final int mVersion; + private final VersionInfo mVersion; @VisibleForTesting final MainHandler mMainHandler; @@ -66,14 +65,14 @@ public class PluginInstanceManager<T extends Plugin> { private final PluginManager mManager; PluginInstanceManager(Context context, String action, PluginListener<T> listener, - boolean allowMultiple, Looper looper, int version, PluginManager manager) { + boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager) { this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, manager, Build.IS_DEBUGGABLE); } @VisibleForTesting PluginInstanceManager(Context context, PackageManager pm, String action, - PluginListener<T> listener, boolean allowMultiple, Looper looper, int version, + PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager, boolean debuggable) { mMainHandler = new MainHandler(Looper.getMainLooper()); mPluginHandler = new PluginHandler(looper); @@ -301,8 +300,14 @@ public class PluginInstanceManager<T extends Plugin> { Context pluginContext = new PluginContextWrapper( mContext.createApplicationContext(info, 0), classLoader); Class<?> pluginClass = Class.forName(cls, true, classLoader); + // TODO: Only create the plugin before version check if we need it for + // legacy version check. T plugin = (T) pluginClass.newInstance(); - if (plugin.getVersion() != mVersion) { + try { + checkVersion(pluginClass, plugin, mVersion); + if (DEBUG) Log.d(TAG, "createPlugin"); + return new PluginInfo(pkg, cls, plugin, pluginContext); + } catch (InvalidVersionException e) { final int icon = mContext.getResources().getIdentifier("tuner", "drawable", mContext.getPackageName()); final int color = Resources.getSystem().getIdentifier( @@ -318,20 +323,18 @@ public class PluginInstanceManager<T extends Plugin> { String label = cls; try { label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString(); - } catch (NameNotFoundException e) { + } catch (NameNotFoundException e2) { } - if (plugin.getVersion() < mVersion) { + if (!e.isTooNew()) { // Localization not required as this will never ever appear in a user build. nb.setContentTitle("Plugin \"" + label + "\" is too old") .setContentText("Contact plugin developer to get an updated" - + " version.\nPlugin version: " + plugin.getVersion() - + "\nSystem version: " + mVersion); + + " version.\n" + e.getMessage()); } else { // Localization not required as this will never ever appear in a user build. nb.setContentTitle("Plugin \"" + label + "\" is too new") .setContentText("Check to see if an OTA is available.\n" - + "Plugin version: " + plugin.getVersion() - + "\nSystem version: " + mVersion); + + e.getMessage()); } Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData( Uri.parse("package://" + component.flattenToString())); @@ -345,13 +348,24 @@ public class PluginInstanceManager<T extends Plugin> { + ", expected " + mVersion); return null; } - if (DEBUG) Log.d(TAG, "createPlugin"); - return new PluginInfo(pkg, cls, plugin, pluginContext); } catch (Exception e) { Log.w(TAG, "Couldn't load plugin: " + pkg, e); return null; } } + + private void checkVersion(Class<?> pluginClass, T plugin, VersionInfo version) + throws InvalidVersionException { + VersionInfo pv = new VersionInfo().addClass(pluginClass); + if (pv.hasVersionInfo()) { + version.checkVersion(pv); + } else { + int fallbackVersion = plugin.getVersion(); + if (fallbackVersion != version.getDefaultVersion()) { + throw new InvalidVersionException("Invalid legacy version", false); + } + } + } } public static class PluginContextWrapper extends ContextWrapper { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java index cef485e673ad..8b4bd7bc9f7b 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java @@ -33,6 +33,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.SystemProperties; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -40,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.plugins.annotations.ProvidesInterface; import dalvik.system.PathClassLoader; @@ -93,7 +95,18 @@ public class PluginManager extends BroadcastReceiver { Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); } - public <T extends Plugin> T getOneShotPlugin(String action, int version) { + public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + return getOneShotPlugin(info.action(), cls); + } + + public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { if (!isDebuggable) { // Never ever ever allow these on production builds, they are only for prototyping. return null; @@ -102,7 +115,7 @@ public class PluginManager extends BroadcastReceiver { throw new RuntimeException("Must be called from UI thread"); } PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null, - false, mBackgroundThread.getLooper(), version, this); + false, mBackgroundThread.getLooper(), cls, this); mPluginPrefs.addAction(action); PluginInfo<T> info = p.getPlugin(); if (info != null) { @@ -114,20 +127,36 @@ public class PluginManager extends BroadcastReceiver { return null; } + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { + addPluginListener(listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + addPluginListener(info.action(), listener, cls, allowMultiple); + } + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version) { - addPluginListener(action, listener, version, false); + Class<?> cls) { + addPluginListener(action, listener, cls, false); } public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version, boolean allowMultiple) { + Class cls, boolean allowMultiple) { if (!isDebuggable) { // Never ever ever allow these on production builds, they are only for prototyping. return; } mPluginPrefs.addAction(action); PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, - allowMultiple, mBackgroundThread.getLooper(), version, this); + allowMultiple, mBackgroundThread.getLooper(), cls, this); p.loadAll(); mPluginMap.put(listener, p); startListening(); @@ -282,9 +311,9 @@ public class PluginManager extends BroadcastReceiver { public static class PluginInstanceManagerFactory { public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, - int version, PluginManager manager) { + Class<?> cls, PluginManager manager) { return new PluginInstanceManager(context, action, listener, allowMultiple, looper, - version, manager); + new VersionInfo().addClass(cls), manager); } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginPrefs.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java index 3671b3c1689f..3671b3c1689f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginPrefs.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java diff --git a/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java b/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java new file mode 100644 index 000000000000..84f7761a8043 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins; + +import com.android.systemui.plugins.annotations.Dependencies; +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.annotations.Requirements; +import com.android.systemui.plugins.annotations.Requires; + +import android.util.ArrayMap; + +public class VersionInfo { + + private final ArrayMap<Class<?>, Version> mVersions = new ArrayMap<>(); + private Class<?> mDefault; + + public boolean hasVersionInfo() { + return !mVersions.isEmpty(); + } + + public int getDefaultVersion() { + return mVersions.get(mDefault).mVersion; + } + + public VersionInfo addClass(Class<?> cls) { + if (mDefault == null) { + // The legacy default version is from the first class we add. + mDefault = cls; + } + addClass(cls, false); + return this; + } + + private void addClass(Class<?> cls, boolean required) { + ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (provider != null) { + mVersions.put(cls, new Version(provider.version(), true)); + } + Requires requires = cls.getDeclaredAnnotation(Requires.class); + if (requires != null) { + mVersions.put(requires.target(), new Version(requires.version(), required)); + } + Requirements requirements = cls.getDeclaredAnnotation(Requirements.class); + if (requirements != null) { + for (Requires r : requirements.value()) { + mVersions.put(r.target(), new Version(r.version(), required)); + } + } + DependsOn depends = cls.getDeclaredAnnotation(DependsOn.class); + if (depends != null) { + addClass(depends.target(), true); + } + Dependencies dependencies = cls.getDeclaredAnnotation(Dependencies.class); + if (dependencies != null) { + for (DependsOn d : dependencies.value()) { + addClass(d.target(), true); + } + } + } + + public void checkVersion(VersionInfo plugin) throws InvalidVersionException { + ArrayMap<Class<?>, Version> versions = new ArrayMap<>(mVersions); + plugin.mVersions.forEach((aClass, version) -> { + Version v = versions.remove(aClass); + if (v == null) { + v = createVersion(aClass); + } + if (v == null) { + throw new InvalidVersionException(aClass.getSimpleName() + + " does not provide an interface", false); + } + if (v.mVersion != version.mVersion) { + throw new InvalidVersionException(aClass, v.mVersion < version.mVersion, v.mVersion, + version.mVersion); + } + }); + versions.forEach((aClass, version) -> { + if (version.mRequired) { + throw new InvalidVersionException("Missing required dependency " + + aClass.getSimpleName(), false); + } + }); + } + + private Version createVersion(Class<?> cls) { + ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (provider != null) { + return new Version(provider.version(), false); + } + return null; + } + + public static class InvalidVersionException extends RuntimeException { + private final boolean mTooNew; + + public InvalidVersionException(String str, boolean tooNew) { + super(str); + mTooNew = tooNew; + } + + public InvalidVersionException(Class<?> cls, boolean tooNew, int expected, int actual) { + super(cls.getSimpleName() + " expected version " + expected + " but had " + actual); + mTooNew = tooNew; + } + + public boolean isTooNew() { + return mTooNew; + } + } + + private static class Version { + + private final int mVersion; + private final boolean mRequired; + + public Version(int version, boolean required) { + mVersion = version; + mRequired = required; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index a231e791e3b0..35592576043d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -242,7 +242,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { maxHeight = height; } } - setMeasuredDimension(getMeasuredWidth(), maxHeight); + setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom()); } private final Runnable mDistribute = new Runnable() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index c85f83ba3588..e0d1cba401a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -160,7 +160,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mAllViews.clear(); mTopFiveQs.clear(); - mAllViews.add((View) mQsPanel.getTileLayout()); + QSTileLayout tileLayout = mQsPanel.getTileLayout(); + mAllViews.add((View) tileLayout); + firstPageBuilder.addFloat(tileLayout, "translationY", mQsPanel.getHeight(), 0); for (QSTile<?> tile : tiles) { QSTileBaseView tileView = mQsPanel.getTileView(tile); @@ -168,7 +170,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha Log.e(TAG, "tileView is null " + tile.getTileSpec()); continue; } - final TextView label = ((QSTileView) tileView).getLabel(); final View tileIcon = tileView.getIcon().getIconView(); View view = mQs.getView(); if (count < mNumQuickTiles && mAllowFancy) { @@ -187,12 +188,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // Counteract the parent translation on the tile. So we have a static base to // animate the label position off from. - firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0); + //firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0); - // Move the real tile's label from the quick tile position to its final + // Move the real tile from the quick tile position to its final // location. - translationXBuilder.addFloat(label, "translationX", -xDiff, 0); - translationYBuilder.addFloat(label, "translationY", -yDiff, 0); + translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); + translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); mTopFiveQs.add(tileView.getIcon()); mAllViews.add(tileView.getIcon()); @@ -209,22 +210,22 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0); translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); - translationYBuilder.addFloat(label, "translationY", -yDiff, 0); + translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0); mAllViews.add(tileIcon); } else { firstPageBuilder.addFloat(tileView, "alpha", 0, 1); + firstPageBuilder.addFloat(tileView, "translationY", -mQsPanel.getHeight(), 0); } mAllViews.add(tileView); - mAllViews.add(label); count++; } if (mAllowFancy) { // Make brightness appear static position and alpha in through second half. View brightness = mQsPanel.getBrightnessView(); if (brightness != null) { -// firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0); + firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0); mBrightnessAnimator = new TouchAnimator.Builder() .addFloat(brightness, "alpha", 0, 1) .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1) @@ -240,7 +241,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // Fade in the tiles/labels as we reach the final position. mFirstPageDelayedAnimator = new TouchAnimator.Builder() .setStartDelay(EXPANDED_TILE_DELAY) - .addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1) + .addFloat(tileLayout, "alpha", 0, 1) .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build(); mAllViews.add(mQsPanel.getFooter().getView()); float px = 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 504678c7f1c5..e8d5ece2072c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -86,6 +86,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback { setOrientation(VERTICAL); + mBrightnessView = LayoutInflater.from(context).inflate( + R.layout.quick_settings_brightness_dialog, this, false); + addView(mBrightnessView); + setupTileLayout(); mFooter = new QSFooter(this, context); @@ -100,10 +104,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback { updateResources(); - mBrightnessView = LayoutInflater.from(context).inflate( - R.layout.quick_settings_brightness_dialog, this, false); - addView(mBrightnessView); - mBrightnessController = new BrightnessController(getContext(), (ImageView) findViewById(R.id.brightness_icon), (ToggleSliderView) findViewById(R.id.brightness_slider)); @@ -441,8 +441,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback { } r.tile.setDetailListening(show); int x = r.tileView.getLeft() + r.tileView.getWidth() / 2; - int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + r.tileView.getHeight() / 2 - + getTop(); + int y; + if (r.tileView instanceof QSTileView) { + View labelContainer = (View) ((QSTileView) r.tileView).getLabel().getParent(); + y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + getTop() + + labelContainer.getTop() + labelContainer.getHeight() / 2; + } else { + y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + r.tileView.getHeight() / 2 + + getTop(); + } handleShowDetailImpl(r, show, x, y); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java index 0e04d0a0c825..c750fdcd9fad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java @@ -15,38 +15,30 @@ */ package com.android.systemui.qs; -import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.TypedArray; -import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.service.quicksettings.Tile; import android.text.TextUtils; -import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.Switch; -import com.android.settingslib.Utils; - import com.android.systemui.R; public class QSTileBaseView extends LinearLayout { private static final String TAG = "QSTileBaseView"; private final H mHandler = new H(); + private final FrameLayout mIconFrame; protected QSIconView mIcon; protected RippleDrawable mRipple; private Drawable mTileBackground; @@ -63,15 +55,15 @@ public class QSTileBaseView extends LinearLayout { // Default to Quick Tile padding, and QSTileView will specify its own padding. int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding); - FrameLayout frame = new FrameLayout(context); - frame.setForegroundGravity(Gravity.CENTER); + mIconFrame = new FrameLayout(context); + mIconFrame.setForegroundGravity(Gravity.CENTER); int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); - addView(frame, new LayoutParams(size, size)); + addView(mIconFrame, new LayoutParams(size, size)); mIcon = icon; FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.setMargins(0, padding, 0, padding); - frame.addView(mIcon, params); + mIconFrame.addView(mIcon, params); mTileBackground = newTileBackground(); if (mTileBackground instanceof RippleDrawable) { @@ -105,7 +97,7 @@ public class QSTileBaseView extends LinearLayout { private void updateRippleSize(int width, int height) { // center the touch feedback on the center of the icon, and dial it down a bit final int cx = width / 2; - final int cy = height / 2; + final int cy = mIconFrame.getMeasuredHeight() / 2; final int rad = (int) (mIcon.getHeight() * .85f); mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 232941dc629b..428fe9bbff7e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -20,6 +20,7 @@ import static com.android.systemui.qs.QSTile.getColorForState; import android.content.Context; import android.content.res.Configuration; +import android.graphics.drawable.RippleDrawable; import android.service.quicksettings.Tile; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; @@ -28,6 +29,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; import android.widget.ImageView; import android.widget.TextView; @@ -43,8 +45,7 @@ public class QSTileView extends QSTileBaseView { protected TextView mLabel; private ImageView mPadLock; private int mState; - private OnClickListener mClick; - private OnClickListener mSecondaryClick; + private ViewGroup mLabelContainer; public QSTileView(Context context, QSIconView icon) { this(context, icon, false); @@ -76,13 +77,15 @@ public class QSTileView extends QSTileBaseView { } protected void createLabel() { - ViewGroup view = (ViewGroup) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_tile_label, null); - view.setClipChildren(false); - view.setClipToPadding(false); - mLabel = (TextView) view.findViewById(R.id.tile_label); - mPadLock = (ImageView) view.findViewById(R.id.restricted_padlock); - addView(view); + mLabelContainer = (ViewGroup) LayoutInflater.from(getContext()) + .inflate(R.layout.qs_tile_label, this, false); + mLabelContainer.setClipChildren(false); + mLabelContainer.setClipToPadding(false); + mLabel = (TextView) mLabelContainer.findViewById(R.id.tile_label); + mPadLock = (ImageView) mLabelContainer.findViewById(R.id.restricted_padlock); + + mLabelContainer.setBackground(newTileBackground()); + addView(mLabelContainer); } @Override @@ -104,17 +107,10 @@ public class QSTileView extends QSTileBaseView { } @Override - public void init(OnClickListener click, OnClickListener secondaryClick, OnLongClickListener longClick) { - mClick = click; - mSecondaryClick = secondaryClick; + public void init(OnClickListener click, OnClickListener secondaryClick, + OnLongClickListener longClick) { super.init(click, secondaryClick, longClick); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - setOnClickListener(event.getY() < (getMeasuredHeight() / 2) ? mClick : mSecondaryClick); - } - return super.onTouchEvent(event); + mLabelContainer.setClickable(true); + mLabelContainer.setOnClickListener(secondaryClick); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index f2c3e61cfe02..4e307973d003 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -301,6 +301,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta if (position == mEditIndex) position--; move(mAccessibilityFromIndex, position, v); + notifyDataSetChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index ce7294204b48..ec4ca7a6233a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -102,12 +102,7 @@ public class TileServices extends IQSService.Stub { mTokenMap.remove(service.getToken()); mTiles.remove(tile.getComponent()); final String slot = tile.getComponent().getClassName(); - mMainHandler.post(new Runnable() { - @Override - public void run() { - mHost.getIconController().removeIcon(slot); - } - }); + mMainHandler.post(() -> mHost.getIconController().removeIcon(slot)); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java index 8227f8f63ceb..f7bfc1e6d4fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java @@ -104,11 +104,6 @@ public class BatteryTile extends QSTile<QSTile.State> implements BatteryControll } @Override - protected void handleSecondaryClick() { - showDetail(true); - } - - @Override public CharSequence getTileLabel() { return mContext.getString(R.string.battery); } @@ -118,7 +113,6 @@ public class BatteryTile extends QSTile<QSTile.State> implements BatteryControll int level = (arg != null) ? (Integer) arg : mLevel; String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0); - state.dualTarget = true; state.state = mCharging ? Tile.STATE_UNAVAILABLE : mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.icon = ResourceIcon.get(R.drawable.ic_qs_battery_saver); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index b48b829d14f7..3c28f7602c18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -123,7 +123,6 @@ public class CastTile extends QSTile<QSTile.BooleanState> { @Override protected void handleUpdateState(BooleanState state, Object arg) { - state.dualTarget = true; state.label = mContext.getString(R.string.quick_settings_cast_title); state.contentDescription = state.label; state.value = false; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index a6fe0ea2ea73..c9debb2068b7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -31,6 +31,7 @@ import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.provider.Settings.Secure; import android.util.Log; import android.view.KeyEvent; import android.view.View; @@ -44,7 +45,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.DejankUtils; import com.android.systemui.Interpolators; import com.android.keyguard.LatencyTracker; -import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; @@ -181,8 +181,10 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // is still valid. Otherwise, we need to reset the lastStackactiveTime to the // currentTime and remove the old tasks in between which would not be previously // visible, but currently would be in the new currentTime - long oldLastStackActiveTime = Prefs.getLong(RecentsActivity.this, - Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1); + int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this) + .getCurrentUser(); + long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser); if (oldLastStackActiveTime != -1) { long currentTime = System.currentTimeMillis(); if (currentTime < oldLastStackActiveTime) { @@ -200,8 +202,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD Recents.getSystemServices().removeTask(task.persistentId); } } - Prefs.putLong(RecentsActivity.this, - Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, currentTime); + Settings.Secure.putLongForUser(RecentsActivity.this.getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, currentTime, currentUser); } } } @@ -834,8 +836,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD Recents.getTaskLoader().dump(prefix, writer); String id = Integer.toHexString(System.identityHashCode(this)); - long lastStackActiveTime = Prefs.getLong(this, - Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1); + long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, + SystemServicesProxy.getInstance(this).getCurrentUser()); writer.print(prefix); writer.print(TAG); writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N"); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 11b598478bfb..12c10dff0ab3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -26,6 +26,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; +import android.provider.Settings.Secure; import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -130,6 +132,7 @@ public class RecentsTaskLoadPlan { preloadRawTasks(includeFrontMostExcludedTask); } + SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext); SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>(); SparseIntArray affiliatedTaskCounts = new SparseIntArray(); SparseBooleanArray lockedUsers = new SparseBooleanArray(); @@ -137,8 +140,10 @@ public class RecentsTaskLoadPlan { R.string.accessibility_recents_item_will_be_dismissed); String appInfoDescFormat = mContext.getString( R.string.accessibility_recents_item_open_app_info); - long lastStackActiveTime = Prefs.getLong(mContext, - Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0); + int currentUserId = ssp.getCurrentUser(); + long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId); + long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId); if (RecentsDebugFlags.Static.EnableMockTasks) { lastStackActiveTime = 0; } @@ -205,8 +210,8 @@ public class RecentsTaskLoadPlan { affiliatedTasks.put(taskKey.id, taskKey); } if (newLastStackActiveTime != -1) { - Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, - newLastStackActiveTime); + Settings.Secure.putLongForUser(mContext.getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId); } // Initialize the stacks @@ -285,4 +290,36 @@ public class RecentsTaskLoadPlan { private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) { return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME); } + + + /** + * Migrate the last active time from the prefs to the secure settings. + * + * The first time this runs, it will: + * 1) fetch the last stack active time from the prefs + * 2) set the prefs to the last stack active time for all users + * 3) clear the pref + * 4) return the last stack active time + * + * Subsequent calls to this will return zero. + */ + private long migrateLegacyLastStackActiveTime(int currentUserId) { + long legacyLastStackActiveTime = Prefs.getLong(mContext, + Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1); + if (legacyLastStackActiveTime != -1) { + Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME); + UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + List<UserInfo> users = userMgr.getUsers(); + for (int i = 0; i < users.size(); i++) { + int userId = users.get(i).id; + if (userId != currentUserId) { + Settings.Secure.putLongForUser(mContext.getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, + userId); + } + } + return legacyLastStackActiveTime; + } + return 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 3bbaf998cc9a..cb4306f0d137 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -22,7 +22,6 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; @@ -331,10 +330,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L)); boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon, NotificationColorUtil.getInstance(mContext)); + int color = StatusBarIconView.NO_COLOR; if (colorize) { - int color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded()); - expandedIcon.setImageTintList(ColorStateList.valueOf(color)); + color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded()); } + expandedIcon.setStaticDrawableColor(color); } private void updateLimits() { @@ -462,6 +462,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { updateBackgroundForGroupState(); updateClickAndFocus(); if (mNotificationParent != null) { + setOverrideTintColor(NO_COLOR, 0.0f); mNotificationParent.updateBackgroundForGroupState(); } updateIconVisibilities(); @@ -1990,7 +1991,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mAboveShelf = aboveShelf; } - public class NotificationViewState extends ExpandableViewState { + public static class NotificationViewState extends ExpandableViewState { private final StackScrollState mOverallState; @@ -2011,8 +2012,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override protected void onYTranslationAnimationFinished(View view) { super.onYTranslationAnimationFinished(view); - if (mHeadsupDisappearRunning) { - setHeadsUpAnimatingAway(false); + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (row.isHeadsUpAnimatingAway()) { + row.setHeadsUpAnimatingAway(false); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index d599ec1a2062..bc992d821998 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -87,6 +87,7 @@ public class KeyguardIndicationController { private KeyguardUpdateMonitorCallback mUpdateMonitor; private final DevicePolicyManager mDevicePolicyManager; + private boolean mDozing; public KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon) { @@ -139,7 +140,7 @@ public class KeyguardIndicationController { return; } - if (mDevicePolicyManager.isDeviceManaged()) { + if (!mDozing && mDevicePolicyManager.isDeviceManaged()) { final CharSequence organizationName = mDevicePolicyManager.getDeviceOwnerOrganizationName(); if (organizationName != null) { @@ -224,6 +225,18 @@ public class KeyguardIndicationController { if (mVisible) { // Walk down a precedence-ordered list of what should indication // should be shown based on user or device state + if (mDozing) { + // If we're dozing, never show a persistent indication. + if (!TextUtils.isEmpty(mTransientIndication)) { + mTextView.switchIndication(mTransientIndication); + mTextView.setTextColor(mTransientTextColor); + + } else { + mTextView.switchIndication(null); + } + return; + } + if (!mUserManager.isUserUnlocked(ActivityManager.getCurrentUser())) { mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked); mTextView.setTextColor(Color.WHITE); @@ -319,6 +332,12 @@ public class KeyguardIndicationController { } }; + public void setDozing(boolean dozing) { + mDozing = dozing; + updateIndication(); + updateDisclosure(); + } + protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { private int mLastSuccessiveErrorMessage = -1; @@ -349,7 +368,8 @@ public class KeyguardIndicationController { int errorColor = mContext.getResources().getColor(R.color.system_warning_color, null); if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(helpString, errorColor); - } else if (updateMonitor.isDeviceInteractive()) { + } else if (updateMonitor.isDeviceInteractive() + || mDozing && updateMonitor.isScreenOn()) { mLockIcon.setTransientFpError(true); showTransientIndication(helpString, errorColor); mHandler.removeMessages(MSG_CLEAR_FP_MSG); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java index 355022f9794c..534a71936b5a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java @@ -90,8 +90,7 @@ public class NotificationMenuRow extends FrameLayout protected void onAttachedToWindow() { super.onAttachedToWindow(); Dependency.get(PluginManager.class).addPluginListener( - NotificationMenuRowProvider.ACTION, this, - NotificationMenuRowProvider.VERSION, false /* Allow multiple */); + this, NotificationMenuRowProvider.class, false /* Allow multiple */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 2425076a4442..d4ed1dca5781 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -23,6 +23,7 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import com.android.internal.widget.CachingIconView; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.ViewInvertHelper; @@ -414,7 +415,8 @@ public class NotificationShelf extends ActivatableNotificationView { transitionAmount); float shelfIconSize = icon.getHeight() * icon.getIconScale(); float alpha = 1.0f; - if (!row.isShowingIcon()) { + boolean noIcon = !row.isShowingIcon(); + if (noIcon) { // The view currently doesn't have an icon, lets transform it in! alpha = transitionAmount; notificationIconSize = shelfIconSize / 2.0f; @@ -438,6 +440,13 @@ public class NotificationShelf extends ActivatableNotificationView { if (row.isAboveShelf()) { iconState.hidden = true; } + int shelfColor = icon.getStaticDrawableColor(); + if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) { + int notificationColor = row.getNotificationHeader().getOriginalNotificationColor(); + shelfColor = NotificationUtils.interpolateColors(notificationColor, shelfColor, + iconState.iconAppearAmount); + } + iconState.iconColor = shelfColor; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 6283148c84f8..aec9a4b421ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -19,9 +19,11 @@ package com.android.systemui.statusbar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.app.Notification; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; @@ -37,7 +39,6 @@ import android.util.FloatProperty; import android.util.Log; import android.util.Property; import android.util.TypedValue; -import android.view.View; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.view.animation.Interpolator; @@ -50,6 +51,9 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import java.text.NumberFormat; public class StatusBarIconView extends AnimatedImageView { + public static final int NO_COLOR = 0; + private final int ANIMATION_DURATION_FAST = 100; + public static final int STATE_ICON = 0; public static final int STATE_DOT = 1; public static final int STATE_HIDDEN = 2; @@ -104,6 +108,17 @@ public class StatusBarIconView extends AnimatedImageView { private ObjectAnimator mDotAnimator; private float mDotAppearAmount; private OnVisibilityChangedListener mOnVisibilityChangedListener; + private int mDrawableColor; + private int mIconColor; + private ValueAnimator mColorAnimator; + private int mCurrentSetColor = NO_COLOR; + private int mAnimationStartColor = NO_COLOR; + private final ValueAnimator.AnimatorUpdateListener mColorUpdater + = animation -> { + int newColor = NotificationUtils.interpolateColors(mAnimationStartColor, mIconColor, + animation.getAnimatedFraction()); + setColorInternal(newColor); + }; public StatusBarIconView(Context context, String slot, Notification notification) { this(context, slot, notification, false); @@ -123,7 +138,7 @@ public class StatusBarIconView extends AnimatedImageView { setScaleType(ScaleType.CENTER); mDensity = context.getResources().getDisplayMetrics().densityDpi; if (mNotification != null) { - setIconTint(getContext().getColor( + setDecorColor(getContext().getColor( com.android.internal.R.color.notification_icon_default_color)); } reloadDimens(); @@ -446,10 +461,66 @@ public class StatusBarIconView extends AnimatedImageView { return c.getString(R.string.accessibility_desc_notification_icon, appName, desc); } - public void setIconTint(int iconTint) { + /** + * Set the color that is used to draw decoration like the overflow dot. This will not be applied + * to the drawable. + */ + public void setDecorColor(int iconTint) { mDotPaint.setColor(iconTint); } + /** + * Set the static color that should be used for the drawable of this icon if it's not + * transitioning this also immediately sets the color. + */ + public void setStaticDrawableColor(int color) { + mDrawableColor = color; + setColorInternal(color); + mIconColor = color; + } + + private void setColorInternal(int color) { + if (color != NO_COLOR) { + setImageTintList(ColorStateList.valueOf(color)); + } else { + setImageTintList(null); + } + mCurrentSetColor = color; + } + + public void setIconColor(int iconColor, boolean animate) { + if (mIconColor != iconColor) { + mIconColor = iconColor; + if (mColorAnimator != null) { + mColorAnimator.cancel(); + } + if (mCurrentSetColor == iconColor) { + return; + } + if (animate && mCurrentSetColor != NO_COLOR) { + mAnimationStartColor = mCurrentSetColor; + mColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); + mColorAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mColorAnimator.setDuration(ANIMATION_DURATION_FAST); + mColorAnimator.addUpdateListener(mColorUpdater); + mColorAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mColorAnimator = null; + mAnimationStartColor = NO_COLOR; + } + }); + mColorAnimator.start(); + } else { + setColorInternal(iconColor); + } + } + } + + public int getStaticDrawableColor() { + return mDrawableColor; + } + public void setVisibleState(int state) { setVisibleState(state, true /* animate */, null /* endRunnable */); } @@ -467,10 +538,13 @@ public class StatusBarIconView extends AnimatedImageView { boolean runnableAdded = false; if (visibleState != mVisibleState) { mVisibleState = visibleState; + if (mIconAppearAnimator != null) { + mIconAppearAnimator.cancel(); + } + if (mDotAnimator != null) { + mDotAnimator.cancel(); + } if (animate) { - if (mIconAppearAnimator != null) { - mIconAppearAnimator.cancel(); - } float targetAmount = 0.0f; Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN; if (visibleState == STATE_ICON) { @@ -482,7 +556,7 @@ public class StatusBarIconView extends AnimatedImageView { mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT, currentAmount, targetAmount); mIconAppearAnimator.setInterpolator(interpolator); - mIconAppearAnimator.setDuration(100); + mIconAppearAnimator.setDuration(ANIMATION_DURATION_FAST); mIconAppearAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -494,9 +568,6 @@ public class StatusBarIconView extends AnimatedImageView { runnableAdded = true; } - if (mDotAnimator != null) { - mDotAnimator.cancel(); - } targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f; interpolator = Interpolators.FAST_OUT_LINEAR_IN; if (visibleState == STATE_DOT) { @@ -508,7 +579,7 @@ public class StatusBarIconView extends AnimatedImageView { mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT, currentAmount, targetAmount); mDotAnimator.setInterpolator(interpolator); - mDotAnimator.setDuration(100); + mDotAnimator.setDuration(ANIMATION_DURATION_FAST); final boolean runRunnable = !runnableAdded; mDotAnimator.addListener(new AnimatorListenerAdapter() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 0e074c7b8710..883a66b59531 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -31,6 +31,7 @@ import java.io.PrintWriter; public class DozeParameters { private static final int MAX_DURATION = 60 * 1000; + public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully"; private final Context mContext; @@ -56,6 +57,10 @@ public class DozeParameters { pw.print(" getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold()); pw.print(" getPickupSubtypePerformsProxCheck(): ");pw.println( dumpPickupSubtypePerformsProxCheck()); + if (Build.IS_DEBUGGABLE) { + pw.print(" getAlwaysOn(): "); pw.println(getAlwaysOn()); + pw.print(" getSensorsWakeUpFully(): "); pw.println(getSensorsWakeUpFully()); + } } private String dumpPickupSubtypePerformsProxCheck() { @@ -118,6 +123,12 @@ public class DozeParameters { Settings.Secure.DOZE_ALWAYS_ON, 0, UserHandle.USER_CURRENT) != 0; } + public boolean getSensorsWakeUpFully() { + return Build.IS_DEBUGGABLE + && Settings.Secure.getIntForUser(mContext.getContentResolver(), + DOZE_SENSORS_WAKE_UP_FULLY, 0, UserHandle.USER_CURRENT) != 0; + } + private boolean getBoolean(String propName, int resId) { return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 2836f41b04f5..8f63d45e04cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -167,6 +167,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private IntentButton mLeftPlugin; private String mLeftButtonStr; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); + private boolean mDozing; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -261,9 +262,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL super.onAttachedToWindow(); mAccessibilityController.addStateChangedCallback(this); Dependency.get(PluginManager.class).addPluginListener(RIGHT_BUTTON_PLUGIN, - mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */); + mRightListener, IntentButtonProvider.class, false /* Only allow one */); Dependency.get(PluginManager.class).addPluginListener(LEFT_BUTTON_PLUGIN, - mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */); + mLeftListener, IntentButtonProvider.class, false /* Only allow one */); Dependency.get(TunerService.class).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON, LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON); } @@ -361,7 +362,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL // Things are not set up yet; reply hazy, ask again later return; } - mRightAffordanceView.setVisibility(mRightButton.getIcon().isVisible + mRightAffordanceView.setVisibility(!mDozing && mRightButton.getIcon().isVisible ? View.VISIBLE : View.GONE); } @@ -375,7 +376,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private void updateLeftAffordanceIcon() { IconState state = mLeftButton.getIcon(); - mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE); + mLeftAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE); mLeftAffordanceView.setImageDrawable(state.drawable, state.tint); mLeftAffordanceView.setContentDescription(state.contentDescription); } @@ -846,6 +847,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } }; + public void setDozing(boolean dozing, boolean animate) { + mDozing = dozing; + + updateCameraVisibility(); + updateLeftAffordanceIcon(); + + if (dozing) { + mLockIcon.setVisibility(INVISIBLE); + } else { + mLockIcon.setVisibility(VISIBLE); + if (animate) { + startFinishDozeAnimation(); + } + } + } + private class DefaultLeftButton implements IntentButton { private IconState mIconState = new IconState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index 5fb99dabfeab..720ca1499bf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -138,8 +138,8 @@ public class NavigationBarInflaterView extends FrameLayout super.onAttachedToWindow(); Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT, NAV_BAR_RIGHT); - Dependency.get(PluginManager.class).addPluginListener(NavBarButtonProvider.ACTION, this, - NavBarButtonProvider.VERSION, true /* Allow multiple */); + Dependency.get(PluginManager.class).addPluginListener(this, + NavBarButtonProvider.class, true /* Allow multiple */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 5d13289ef9cc..ad875f13d27e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -783,8 +783,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav protected void onAttachedToWindow() { super.onAttachedToWindow(); onPluginDisconnected(null); // Create default gesture helper - Dependency.get(PluginManager.class).addPluginListener(NavGesture.ACTION, this, - NavGesture.VERSION, false /* Only one */); + Dependency.get(PluginManager.class).addPluginListener(this, + NavGesture.class, false /* Only one */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 6d7ab479d74d..707997d5be45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -1,7 +1,6 @@ package com.android.systemui.statusbar.phone; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; @@ -226,12 +225,13 @@ public class NotificationIconAreaController implements DarkReceiver { for (int i = 0; i < mNotificationIcons.getChildCount(); i++) { StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i); boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L)); + int color = StatusBarIconView.NO_COLOR; boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil); if (colorize) { - v.setImageTintList(ColorStateList.valueOf( - DarkIconDispatcher.getTint(mTintArea, v, mIconTint))); + color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint); } - v.setIconTint(mIconTint); + v.setStaticDrawableColor(color); + v.setDecorColor(mIconTint); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 571ae2653948..dc5f98c13c6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -446,6 +446,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { public boolean useFullTransitionAmount; public boolean useLinearTransitionAmount; public boolean translateContent; + public int iconColor = StatusBarIconView.NO_COLOR; @Override public void applyToView(View view) { @@ -505,6 +506,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } } icon.setVisibleState(visibleState, animationsAllowed); + icon.setIconColor(iconColor, needsCannedAnimation && animationsAllowed); if (animate) { animateTo(icon, animationProperties); } else { @@ -515,6 +517,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { needsCannedAnimation = false; } + @Override + public void initFrom(View view) { + super.initFrom(view); + if (view instanceof StatusBarIconView) { + iconColor = ((StatusBarIconView) view).getStaticDrawableColor(); + } + } + protected void onYTranslationAnimationFinished(View view) { if (hidden) { view.setVisibility(INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 5da3a101b4bb..6da9e9022c88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -1151,9 +1151,7 @@ public class NotificationPanelView extends PanelView implements .start(); } else if (statusBarState == StatusBarState.KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) { - if (!mDozing) { - mKeyguardBottomArea.setVisibility(View.VISIBLE); - } + mKeyguardBottomArea.setVisibility(View.VISIBLE); mKeyguardBottomArea.setAlpha(1f); } else { mKeyguardBottomArea.setVisibility(View.GONE); @@ -2103,13 +2101,12 @@ public class NotificationPanelView extends PanelView implements private void updateDozingVisibilities(boolean animate) { if (mDozing) { mKeyguardStatusBar.setVisibility(View.INVISIBLE); - mKeyguardBottomArea.setVisibility(View.INVISIBLE); + mKeyguardBottomArea.setDozing(mDozing, animate); } else { - mKeyguardBottomArea.setVisibility(View.VISIBLE); mKeyguardStatusBar.setVisibility(View.VISIBLE); + mKeyguardBottomArea.setDozing(mDozing, animate); if (animate) { animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION); - mKeyguardBottomArea.startFinishDozeAnimation(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java index 457ed6cc4237..ade1b0b6844e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java @@ -26,7 +26,6 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; -import android.icu.text.NumberFormat; import android.os.UserManager; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -55,7 +54,6 @@ import com.android.systemui.qs.QuickQSPanel; import com.android.systemui.qs.TouchAnimator; import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.statusbar.SignalClusterView; -import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; import com.android.systemui.statusbar.policy.NetworkController.IconState; @@ -68,7 +66,7 @@ import com.android.systemui.tuner.TunerService; public class QuickStatusBarHeader extends BaseStatusBarHeader implements NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener, - BatteryStateChangeCallback, SignalCallback { + SignalCallback { private static final float EXPAND_INDICATOR_THRESHOLD = .93f; private ActivityStarter mActivityStarter; @@ -110,7 +108,6 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements private boolean mShowFullAlarm; private float mDateTimeTranslation; - private TextView mBatteryLevel; private SparseBooleanArray mRoamingsBySubId = new SparseBooleanArray(); private boolean mIsRoaming; @@ -161,8 +158,6 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements mAlarmStatus = (TextView) findViewById(R.id.alarm_status); mAlarmStatus.setOnClickListener(this); - mBatteryLevel = (TextView) findViewById(R.id.battery_level); - mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch); mMultiUserAvatar = (ImageView) mMultiUserSwitch.findViewById(R.id.multi_user_avatar); mAlwaysShowMultiUserSwitch = res.getBoolean(R.bool.config_alwaysShowMultiUserSwitcher); @@ -179,7 +174,9 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground); float intensity = colorForeground == Color.WHITE ? 0 : 1; cluster.onDarkChanged(new Rect(0, 0, 0, 0), intensity, colorForeground); + BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery); + battery.forceShowPercent(); int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary); battery.setRawColors(colorForeground, colorSecondary); @@ -443,17 +440,6 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements } } - @Override - public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0); - mBatteryLevel.setText(percentage); - } - - @Override - public void onPowerSaveChanged(boolean isPowerSave) { - // Don't care. - } - public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, String description, boolean isWide, int subId, boolean roaming) { 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 ac13cf4eb111..3d6e4b3cd3b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1129,7 +1129,7 @@ public class StatusBar extends SystemUI implements DemoMode, .replace(R.id.qs_frame, new QSFragment(), QS.TAG) .commit(); new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class) - .startListening(QS.ACTION, QS.VERSION); + .startListening(); final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController); mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow); @@ -4322,6 +4322,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanel.setDozing(mDozing, animate); mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation); mScrimController.setDozing(mDozing); + mKeyguardIndicationController.setDozing(mDozing); // Immediately abort the dozing from the doze scrim controller in case of wake-and-unlock // for pulsing so the Keyguard fade-out animation scrim can take over. @@ -6937,7 +6938,10 @@ public class StatusBar extends SystemUI implements DemoMode, return false; } - if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) { + // Allow peeking for DEFAULT notifications only if we're on Ambient Display. + int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT + : NotificationManager.IMPORTANCE_HIGH; + if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java new file mode 100644 index 000000000000..e50fd5e2c5c5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.tuner; + +import android.annotation.Nullable; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.os.Bundle; +import android.support.v14.preference.ListPreferenceDialogFragment; +import android.support.v7.preference.ListPreference; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class CustomListPreference extends ListPreference { + + public CustomListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CustomListPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + OnClickListener listener) { + } + + protected void onDialogClosed(boolean positiveResult) { + } + + protected Dialog onDialogCreated(DialogFragment fragment, Dialog dialog) { + return dialog; + } + + protected boolean isAutoClosePreference() { + return true; + } + + /** + * Called when a user is about to choose the given value, to determine if we + * should show a confirmation dialog. + * + * @param value the value the user is about to choose + * @return the message to show in a confirmation dialog, or {@code null} to + * not request confirmation + */ + protected CharSequence getConfirmationMessage(String value) { + return null; + } + + protected void onDialogStateRestored(DialogFragment fragment, Dialog dialog, + Bundle savedInstanceState) { + } + + public static class CustomListPreferenceDialogFragment extends ListPreferenceDialogFragment { + + private static final String KEY_CLICKED_ENTRY_INDEX + = "settings.CustomListPrefDialog.KEY_CLICKED_ENTRY_INDEX"; + + private int mClickedDialogEntryIndex; + + public static ListPreferenceDialogFragment newInstance(String key) { + final ListPreferenceDialogFragment fragment = new CustomListPreferenceDialogFragment(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + return fragment; + } + + public CustomListPreference getCustomizablePreference() { + return (CustomListPreference) getPreference(); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + mClickedDialogEntryIndex = getCustomizablePreference() + .findIndexOfValue(getCustomizablePreference().getValue()); + getCustomizablePreference().onPrepareDialogBuilder(builder, getOnItemClickListener()); + if (!getCustomizablePreference().isAutoClosePreference()) { + builder.setPositiveButton(com.android.internal.R.string.ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + onItemConfirmed(); + } + }); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + if (savedInstanceState != null) { + mClickedDialogEntryIndex = savedInstanceState.getInt(KEY_CLICKED_ENTRY_INDEX, + mClickedDialogEntryIndex); + } + return getCustomizablePreference().onDialogCreated(this, dialog); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(KEY_CLICKED_ENTRY_INDEX, mClickedDialogEntryIndex); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getCustomizablePreference().onDialogStateRestored(this, getDialog(), savedInstanceState); + } + + protected OnClickListener getOnItemClickListener() { + return new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + setClickedDialogEntryIndex(which); + if (getCustomizablePreference().isAutoClosePreference()) { + onItemConfirmed(); + } + } + }; + } + + protected void setClickedDialogEntryIndex(int which) { + mClickedDialogEntryIndex = which; + } + + private String getValue() { + final ListPreference preference = getCustomizablePreference(); + if (mClickedDialogEntryIndex >= 0 && preference.getEntryValues() != null) { + return preference.getEntryValues()[mClickedDialogEntryIndex].toString(); + } else { + return null; + } + } + + protected void onItemConfirmed() { + onClick(getDialog(), DialogInterface.BUTTON_POSITIVE); + getDialog().dismiss(); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + getCustomizablePreference().onDialogClosed(positiveResult); + final ListPreference preference = getCustomizablePreference(); + final String value = getValue(); + if (positiveResult && value != null) { + if (preference.callChangeListener(value)) { + preference.setValue(value); + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java index 6f4a3a4969fa..410d3d2e1b53 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java @@ -80,10 +80,8 @@ public class LockscreenFragment extends PreferenceFragment { mTunerService = Dependency.get(TunerService.class); mHandler = new Handler(); addPreferencesFromResource(R.xml.lockscreen_settings); - setupGroup((PreferenceGroup) findPreference(KEY_LEFT), LOCKSCREEN_LEFT_BUTTON, - LOCKSCREEN_LEFT_UNLOCK); - setupGroup((PreferenceGroup) findPreference(KEY_RIGHT), LOCKSCREEN_RIGHT_BUTTON, - LOCKSCREEN_RIGHT_UNLOCK); + setupGroup(LOCKSCREEN_LEFT_BUTTON, LOCKSCREEN_LEFT_UNLOCK); + setupGroup(LOCKSCREEN_RIGHT_BUTTON, LOCKSCREEN_RIGHT_UNLOCK); } @Override @@ -92,30 +90,14 @@ public class LockscreenFragment extends PreferenceFragment { mTunables.forEach(t -> mTunerService.removeTunable(t)); } - private void setupGroup(PreferenceGroup group, String buttonSetting, String unlockKey) { - SwitchPreference customize = (SwitchPreference) group.findPreference(KEY_CUSTOMIZE); - Preference shortcut = group.findPreference(KEY_SHORTCUT); - SwitchPreference unlock = (SwitchPreference) group.findPreference(unlockKey); + private void setupGroup(String buttonSetting, String unlockKey) { + Preference shortcut = findPreference(buttonSetting); + SwitchPreference unlock = (SwitchPreference) findPreference(unlockKey); addTunable((k, v) -> { - boolean visible = v != null; - customize.setChecked(visible); - shortcut.setVisible(visible); + boolean visible = !TextUtils.isEmpty(v); unlock.setVisible(visible); - if (visible) { - setSummary(shortcut, v); - } + setSummary(shortcut, v); }, buttonSetting); - customize.setOnPreferenceChangeListener((preference, newValue) -> { - boolean hasSetting = mTunerService.getValue(buttonSetting) != null; - if (hasSetting != (boolean) newValue) { - mHandler.post(() -> mTunerService.setValue(buttonSetting, hasSetting ? null : "")); - } - return true; - }); - shortcut.setOnPreferenceClickListener(preference -> { - showSelectDialog(buttonSetting); - return true; - }); } private void showSelectDialog(String buttonSetting) { @@ -129,24 +111,15 @@ public class LockscreenFragment extends PreferenceFragment { mTunerService.setValue(buttonSetting, item.getSettingValue()); dialog.dismiss(); }); - LauncherApps apps = getContext().getSystemService(LauncherApps.class); - List<LauncherActivityInfo> activities = apps.getActivityList(null, - Process.myUserHandle()); - - activities.forEach(info -> { - App app = new App(getContext(), info); - try { - new ShortcutParser(getContext(), info.getComponentName()).getShortcuts().forEach( - shortcut -> app.addChild(new StaticShortcut(getContext(), shortcut))); - } catch (NameNotFoundException e) { - } - adapter.addItem(app); - }); v.setAdapter(adapter); } private void setSummary(Preference shortcut, String value) { + if (value == null) { + shortcut.setSummary(R.string.lockscreen_none); + return; + } if (value.contains("::")) { Shortcut info = getShortcutInfo(getContext(), value); shortcut.setSummary(info != null ? info.label : null); @@ -155,7 +128,7 @@ public class LockscreenFragment extends PreferenceFragment { shortcut.setSummary(info != null ? info.loadLabel(getContext().getPackageManager()) : null); } else { - shortcut.setSummary(null); + shortcut.setSummary(R.string.lockscreen_none); } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java index 28a00570a019..45abd456ea16 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java @@ -33,6 +33,9 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.FontMetricsInt; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -58,7 +61,7 @@ import com.android.systemui.tuner.TunerService.Tunable; import java.util.ArrayList; -public class NavBarTuner extends PreferenceFragment { +public class NavBarTuner extends TunerPreferenceFragment { private static final String LAYOUT = "layout"; private static final String LEFT = "left"; @@ -68,13 +71,13 @@ public class NavBarTuner extends PreferenceFragment { private static final String KEYCODE = "keycode"; private static final String ICON = "icon"; - private static final int[] ICONS = new int[]{ - R.drawable.ic_qs_circle, - R.drawable.ic_add, - R.drawable.ic_remove, - R.drawable.ic_left, - R.drawable.ic_right, - R.drawable.ic_menu, + private static final int[][] ICONS = new int[][]{ + {R.drawable.ic_qs_circle, R.string.tuner_circle}, + {R.drawable.ic_add, R.string.tuner_plus}, + {R.drawable.ic_remove, R.string.tuner_minus}, + {R.drawable.ic_left, R.string.tuner_left}, + {R.drawable.ic_right, R.string.tuner_right}, + {R.drawable.ic_menu, R.string.tuner_menu}, }; private final ArrayList<Tunable> mTunables = new ArrayList<>(); @@ -96,10 +99,8 @@ public class NavBarTuner extends PreferenceFragment { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.nav_bar_tuner); bindLayout((ListPreference) findPreference(LAYOUT)); - bindButton((PreferenceCategory) findPreference(LEFT), - NAV_BAR_LEFT, NAVSPACE); - bindButton((PreferenceCategory) findPreference(RIGHT), - NAV_BAR_RIGHT, MENU_IME); + bindButton(NAV_BAR_LEFT, NAVSPACE, LEFT); + bindButton(NAV_BAR_RIGHT, MENU_IME, RIGHT); } @Override @@ -129,9 +130,8 @@ public class NavBarTuner extends PreferenceFragment { }); } - private void bindButton(PreferenceCategory parent, String setting, String def) { - String k = parent.getKey(); - DropDownPreference type = (DropDownPreference) findPreference(TYPE + "_" + k); + private void bindButton(String setting, String def, String k) { + ListPreference type = (ListPreference) findPreference(TYPE + "_" + k); Preference keycode = findPreference(KEYCODE + "_" + k); ListPreference icon = (ListPreference) findPreference(ICON + "_" + k); setupIcons(icon); @@ -195,8 +195,14 @@ public class NavBarTuner extends PreferenceFragment { .loadDrawable(getContext()); d.setTint(Color.BLACK); d.setBounds(0, 0, size, size); - ImageSpan span = new ImageSpan(d); + ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE); builder.append(" ", span, 0); + builder.append(" "); + for (int i = 0; i < ICONS.length; i++) { + if (ICONS[i][0] == id) { + builder.append(getString(ICONS[i][1])); + } + } icon.setSummary(builder); } catch (Exception e) { Log.d("NavButton", "Problem with summary", e); @@ -204,7 +210,7 @@ public class NavBarTuner extends PreferenceFragment { } } - private void setValue(String setting, DropDownPreference type, Preference keycode, + private void setValue(String setting, ListPreference type, Preference keycode, ListPreference icon) { String button = type.getValue(); if (KEY.equals(button)) { @@ -226,14 +232,16 @@ public class NavBarTuner extends PreferenceFragment { getContext().getResources().getDisplayMetrics()); for (int i = 0; i < ICONS.length; i++) { SpannableStringBuilder builder = new SpannableStringBuilder(); - Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i]) + Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i][0]) .loadDrawable(getContext()); d.setTint(Color.BLACK); d.setBounds(0, 0, size, size); - ImageSpan span = new ImageSpan(d); + ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE); builder.append(" ", span, 0); + builder.append(" "); + builder.append(getString(ICONS[i][1])); labels[i] = builder; - values[i] = getContext().getPackageName() + "/" + ICONS[i]; + values[i] = getContext().getPackageName() + "/" + ICONS[i][0]; } icon.setEntries(labels); icon.setEntryValues(values); diff --git a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java new file mode 100644 index 000000000000..dc0d8bf6e668 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.tuner; + +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface.OnClickListener; +import android.os.Bundle; +import android.support.v14.preference.PreferenceFragment; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Toolbar; + +import com.android.settingslib.Utils; +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.R; + +import libcore.util.Objects; + +public class RadioListPreference extends CustomListPreference { + + private OnClickListener mOnClickListener; + private CharSequence mSummary; + + public RadioListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onPrepareDialogBuilder(Builder builder, OnClickListener listener) { + mOnClickListener = listener; + } + + @Override + public void setSummary(CharSequence summary) { + super.setSummary(summary); + mSummary = summary; + } + + @Override + public CharSequence getSummary() { + if (mSummary == null || mSummary.toString().contains("%s")) { + return super.getSummary(); + } + return mSummary; + } + + @Override + protected Dialog onDialogCreated(DialogFragment fragment, Dialog dialog) { + Dialog d = new Dialog(getContext(), android.R.style.Theme_DeviceDefault_Settings); + Toolbar t = (Toolbar) d.findViewById(com.android.internal.R.id.action_bar); + View v = new View(getContext()); + v.setId(R.id.content); + d.setContentView(v); + t.setTitle(getTitle()); + t.setNavigationIcon(Utils.getDrawable(d.getContext(), android.R.attr.homeAsUpIndicator)); + t.setNavigationOnClickListener(view -> d.dismiss()); + + RadioFragment f = new RadioFragment(); + f.setPreference(this); + FragmentHostManager.get(v).getFragmentManager() + .beginTransaction() + .add(android.R.id.content, f) + .commit(); + return d; + } + + @Override + protected void onDialogStateRestored(DialogFragment fragment, Dialog dialog, + Bundle savedInstanceState) { + super.onDialogStateRestored(fragment, dialog, savedInstanceState); + View view = dialog.findViewById(R.id.content); + RadioFragment radioFragment = (RadioFragment) FragmentHostManager.get(view) + .getFragmentManager().findFragmentById(R.id.content); + if (radioFragment != null) { + radioFragment.setPreference(this); + } + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + } + + public static class RadioFragment extends TunerPreferenceFragment { + private RadioListPreference mListPref; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getPreferenceManager().getContext(); + PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(context); + setPreferenceScreen(screen); + if (mListPref != null) { + update(); + } + } + + private void update() { + Context context = getPreferenceManager().getContext(); + + CharSequence[] entries = mListPref.getEntries(); + CharSequence[] values = mListPref.getEntryValues(); + CharSequence current = mListPref.getValue(); + for (int i = 0; i < entries.length; i++) { + CharSequence entry = entries[i]; + SelectablePreference pref = new SelectablePreference(context); + getPreferenceScreen().addPreference(pref); + pref.setTitle(entry); + pref.setChecked(Objects.equal(current, values[i])); + pref.setKey(String.valueOf(i)); + } + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + mListPref.mOnClickListener.onClick(null, Integer.parseInt(preference.getKey())); + return true; + } + + public void setPreference(RadioListPreference radioListPreference) { + mListPref = radioListPreference; + if (getPreferenceManager() != null) { + update(); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java new file mode 100644 index 000000000000..1d1570846a4f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.tuner; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v7.preference.CheckBoxPreference; +import android.util.TypedValue; + +import com.android.systemui.statusbar.ScalingDrawableWrapper; + +public class SelectablePreference extends CheckBoxPreference { + private final int mSize; + + public SelectablePreference(Context context) { + super(context); + setWidgetLayoutResource(com.android.systemui.R.layout.preference_widget_radiobutton); + setSelectable(true); + mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, + context.getResources().getDisplayMetrics()); + } + + @Override + public void setIcon(Drawable icon) { + super.setIcon(new ScalingDrawableWrapper(icon, + mSize / (float) icon.getIntrinsicWidth())); + } + + @Override + public String toString() { + return ""; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java new file mode 100644 index 000000000000..533388a1a5d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.tuner; + +import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON; + +import android.content.Context; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.os.Process; +import android.support.v14.preference.PreferenceFragment; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; +import android.support.v7.preference.PreferenceViewHolder; + +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.tuner.ShortcutParser.Shortcut; +import com.android.systemui.tuner.TunerService.Tunable; + +import java.util.ArrayList; +import java.util.List; + +public class ShortcutPicker extends PreferenceFragment implements Tunable { + + private final ArrayList<SelectablePreference> mSelectablePreferences = new ArrayList<>(); + private String mKey; + private SelectablePreference mNonePreference; + private TunerService mTunerService; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getPreferenceManager().getContext(); + PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(context); + screen.setOrderingAsAdded(true); + PreferenceCategory otherApps = new PreferenceCategory(context); + otherApps.setTitle(R.string.tuner_other_apps); + + mNonePreference = new SelectablePreference(context); + mSelectablePreferences.add(mNonePreference); + mNonePreference.setTitle(R.string.lockscreen_none); + mNonePreference.setIcon(R.drawable.ic_remove_circle); + screen.addPreference(mNonePreference); + + LauncherApps apps = getContext().getSystemService(LauncherApps.class); + List<LauncherActivityInfo> activities = apps.getActivityList(null, + Process.myUserHandle()); + + screen.addPreference(otherApps); + activities.forEach(info -> { + try { + List<Shortcut> shortcuts = new ShortcutParser(getContext(), + info.getComponentName()).getShortcuts(); + AppPreference appPreference = new AppPreference(context, info); + mSelectablePreferences.add(appPreference); + if (shortcuts.size() != 0) { + //PreferenceCategory category = new PreferenceCategory(context); + //screen.addPreference(category); + //category.setTitle(info.getLabel()); + screen.addPreference(appPreference); + shortcuts.forEach(shortcut -> { + ShortcutPreference shortcutPref = new ShortcutPreference(context, shortcut, + info.getLabel()); + mSelectablePreferences.add(shortcutPref); + screen.addPreference(shortcutPref); + }); + return; + } + otherApps.addPreference(appPreference); + } catch (NameNotFoundException e) { + } + }); + // Move other apps to the bottom. + screen.removePreference(otherApps); + for (int i = 0; i < otherApps.getPreferenceCount(); i++) { + Preference p = otherApps.getPreference(0); + otherApps.removePreference(p); + p.setOrder(Preference.DEFAULT_ORDER); + screen.addPreference(p); + } + //screen.addPreference(otherApps); + + setPreferenceScreen(screen); + mKey = getArguments().getString(ARG_PREFERENCE_ROOT); + mTunerService = Dependency.get(TunerService.class); + mTunerService.addTunable(this, mKey); + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + mTunerService.setValue(mKey, preference.toString()); + getActivity().onBackPressed(); + return true; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (LOCKSCREEN_LEFT_BUTTON.equals(mKey)) { + getActivity().setTitle(R.string.lockscreen_shortcut_left); + } else { + getActivity().setTitle(R.string.lockscreen_shortcut_right); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + mTunerService.removeTunable(this); + } + + @Override + public void onTuningChanged(String key, String newValue) { + String v = newValue != null ? newValue : ""; + mSelectablePreferences.forEach(p -> p.setChecked(v.equals(p.toString()))); + } + + private static class AppPreference extends SelectablePreference { + private final LauncherActivityInfo mInfo; + private boolean mBinding; + + public AppPreference(Context context, LauncherActivityInfo info) { + super(context); + mInfo = info; + setTitle(context.getString(R.string.tuner_launch_app, info.getLabel())); + setSummary(context.getString(R.string.tuner_app, info.getLabel())); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + mBinding = true; + if (getIcon() == null) { + setIcon(mInfo.getBadgedIcon( + getContext().getResources().getConfiguration().densityDpi)); + } + mBinding = false; + super.onBindViewHolder(holder); + } + + @Override + protected void notifyChanged() { + if (mBinding) return; + super.notifyChanged(); + } + + @Override + public String toString() { + return mInfo.getComponentName().flattenToString(); + } + } + + private static class ShortcutPreference extends SelectablePreference { + private final Shortcut mShortcut; + private boolean mBinding; + + public ShortcutPreference(Context context, Shortcut shortcut, CharSequence appLabel) { + super(context); + mShortcut = shortcut; + setTitle(shortcut.label); + setSummary(context.getString(R.string.tuner_app, appLabel)); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + mBinding = true; + if (getIcon() == null) { + setIcon(mShortcut.icon.loadDrawable(getContext())); + } + mBinding = false; + super.onBindViewHolder(holder); + } + + @Override + protected void notifyChanged() { + if (mBinding) return; + super.notifyChanged(); + } + + @Override + public String toString() { + return mShortcut.toString(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java index 74280a315e67..4eb1db6ca670 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java @@ -22,10 +22,12 @@ import android.support.v14.preference.PreferenceFragment; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; import android.util.Log; +import android.view.MenuItem; import com.android.settingslib.drawer.SettingsDrawerActivity; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.fragments.FragmentService; public class TunerActivity extends SettingsDrawerActivity implements PreferenceFragment.OnPreferenceStartFragmentCallback, @@ -51,6 +53,22 @@ public class TunerActivity extends SettingsDrawerActivity implements } @Override + protected void onDestroy() { + super.onDestroy(); + Dependency.destroy(FragmentService.class, s -> s.destroyAll()); + Dependency.clearDependencies(); + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onMenuItemSelected(featureId, item); + } + + @Override public void onBackPressed() { if (!getFragmentManager().popBackStackImmediate()) { super.onBackPressed(); @@ -62,6 +80,9 @@ public class TunerActivity extends SettingsDrawerActivity implements try { Class<?> cls = Class.forName(pref.getFragment()); Fragment fragment = (Fragment) cls.newInstance(); + final Bundle b = new Bundle(1); + b.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey()); + fragment.setArguments(b); FragmentTransaction transaction = getFragmentManager().beginTransaction(); setTitle(pref.getTitle()); transaction.replace(R.id.content_frame, fragment); diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java new file mode 100644 index 000000000000..06d40da4557e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.tuner; + +import android.app.DialogFragment; +import android.os.Bundle; +import android.support.v14.preference.PreferenceFragment; +import android.support.v7.preference.Preference; + +public abstract class TunerPreferenceFragment extends PreferenceFragment { + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + DialogFragment f = null; + if (preference instanceof CustomListPreference) { + f = CustomListPreference.CustomListPreferenceDialogFragment + .newInstance(preference.getKey()); + } else { + super.onDisplayPreferenceDialog(preference); + } + f.setTargetFragment(this, 0); + f.show(getFragmentManager(), "dialog_preference"); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index c3948258f11c..32afee9744d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -289,4 +289,12 @@ public class DozeMachineTest { assertEquals(DOZE_PULSING, mMachine.getState()); } + + @Test + @UiThreadTest + public void testWakeUp_wakesUp() { + mMachine.wakeUp(); + + assertTrue(mServiceFake.requestedWakeup); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java index d12fc2cf33a0..c1e7fe46de8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java @@ -22,6 +22,7 @@ public class DozeServiceFake implements DozeMachine.Service { public boolean finished; public int screenState; + public boolean requestedWakeup; public DozeServiceFake() { reset(); @@ -41,4 +42,9 @@ public class DozeServiceFake implements DozeMachine.Service { finished = false; screenState = Display.STATE_UNKNOWN; } + + @Override + public void requestWakeUp() { + requestedWakeup = true; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java index 3715df2a42c0..658966cdde17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java @@ -17,14 +17,27 @@ package com.android.systemui.plugins; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; - import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.plugins.VersionInfo.InvalidVersionException; +import com.android.systemui.plugins.annotations.Requires; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + import android.app.Activity; import android.app.NotificationManager; import android.content.BroadcastReceiver; @@ -35,7 +48,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.HandlerThread; @@ -44,17 +56,6 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -72,6 +73,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PluginListener mMockListener; private PluginInstanceManager mPluginInstanceManager; private PluginManager mMockManager; + private VersionInfo mMockVersionInfo; @Before public void setup() throws Exception { @@ -83,8 +85,10 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mMockManager = mock(PluginManager.class); when(mMockManager.getClassLoader(any(), any())) .thenReturn(getClass().getClassLoader()); + mMockVersionInfo = mock(VersionInfo.class); mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", - mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, true); + mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo, + mMockManager, true); sMockPlugin = mock(Plugin.class); when(sMockPlugin.getVersion()).thenReturn(1); } @@ -145,7 +149,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { NotificationManager nm = mock(NotificationManager.class); mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm); setupFakePmQuery(); - when(sMockPlugin.getVersion()).thenReturn(2); + doThrow(new InvalidVersionException("", false)).when(mMockVersionInfo).checkVersion(any()); mPluginInstanceManager.loadAll(); @@ -181,7 +185,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase { public void testNonDebuggable() throws Exception { // Create a version that thinks the build is not debuggable. mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", - mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, false); + mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo, + mMockManager, false); setupFakePmQuery(); mPluginInstanceManager.loadAll(); @@ -270,6 +275,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase { } } + // This target class doesn't matter, it just needs to have a Requires to hit the flow where + // the mock version info is called. + @Requires(target = PluginManagerTest.class, version = 1) public static class TestPlugin implements Plugin { @Override public int getVersion() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java index a58407b2ae23..09ac5a633cb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java @@ -32,6 +32,7 @@ import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory; @@ -63,7 +64,7 @@ public class PluginManagerTest extends SysuiTestCase { mMockFactory = mock(PluginInstanceManagerFactory.class); mMockPluginInstance = mock(PluginInstanceManager.class); when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(), - Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt(), Mockito.any())) + Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(mMockPluginInstance); mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler); resetExceptionHandler(); @@ -76,20 +77,20 @@ public class PluginManagerTest extends SysuiTestCase { Plugin mockPlugin = mock(Plugin.class); when(mMockPluginInstance.getPlugin()).thenReturn(new PluginInfo(null, null, mockPlugin, null)); - Plugin result = mPluginManager.getOneShotPlugin("myAction", 1); + Plugin result = mPluginManager.getOneShotPlugin("myAction", TestPlugin.class); assertTrue(result == mockPlugin); } @Test public void testAddListener() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); verify(mMockPluginInstance).loadAll(); } @Test public void testRemoveListener() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); mPluginManager.removePluginListener(mMockListener); verify(mMockPluginInstance).destroy(); @@ -101,16 +102,16 @@ public class PluginManagerTest extends SysuiTestCase { mMockExceptionHandler); resetExceptionHandler(); - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); verify(mMockPluginInstance, Mockito.never()).loadAll(); - assertNull(mPluginManager.getOneShotPlugin("myPlugin", 1)); + assertNull(mPluginManager.getOneShotPlugin("myPlugin", TestPlugin.class)); verify(mMockPluginInstance, Mockito.never()).getPlugin(); } @Test public void testExceptionHandler_foundPlugin() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true); mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable()); @@ -125,7 +126,7 @@ public class PluginManagerTest extends SysuiTestCase { @Test public void testExceptionHandler_noFoundPlugin() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false); mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable()); @@ -161,4 +162,10 @@ public class PluginManagerTest extends SysuiTestCase { // Set back the real exception handler so the test can crash if it wants to. Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler); } + + @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION) + public static interface TestPlugin extends Plugin { + public static final String ACTION = "testAction"; + public static final int VERSION = 1; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java new file mode 100644 index 000000000000..0d87d6b06c4e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.VersionInfo.InvalidVersionException; +import com.android.systemui.plugins.annotations.Requires; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QS.Callback; +import com.android.systemui.plugins.qs.QS.DetailAdapter; +import com.android.systemui.plugins.qs.QS.HeightListener; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class VersionInfoTest extends SysuiTestCase { + + @Rule + public ExpectedException mThrown = ExpectedException.none(); + + @Test + public void testHasInfo() { + VersionInfo info = new VersionInfo(); + info.addClass(VersionInfoTest.class); // Has no annotations. + assertFalse(info.hasVersionInfo()); + + info.addClass(OverlayPlugin.class); + assertTrue(info.hasVersionInfo()); + } + + @Test + public void testSingleProvides() { + VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class); + VersionInfo impl = new VersionInfo().addClass(OverlayImpl.class); + overlay.checkVersion(impl); + } + + @Test + public void testIncorrectVersion() { + VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class); + VersionInfo impl = new VersionInfo().addClass(OverlayImplIncorrectVersion.class); + mThrown.expect(InvalidVersionException.class); + overlay.checkVersion(impl); + } + + @Test + public void testMissingRequired() { + VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class); + VersionInfo impl = new VersionInfo(); + mThrown.expect(InvalidVersionException.class); + overlay.checkVersion(impl); + } + + @Test + public void testMissingDependencies() { + VersionInfo overlay = new VersionInfo().addClass(QS.class); + VersionInfo impl = new VersionInfo().addClass(QSImplNoDeps.class); + mThrown.expect(InvalidVersionException.class); + overlay.checkVersion(impl); + } + + @Test + public void testHasDependencies() { + VersionInfo overlay = new VersionInfo().addClass(QS.class); + VersionInfo impl = new VersionInfo().addClass(QSImpl.class); + overlay.checkVersion(impl); + } + + @Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION) + public static class OverlayImpl { + } + + @Requires(target = OverlayPlugin.class, version = 0) + public static class OverlayImplIncorrectVersion { + } + + @Requires(target = QS.class, version = QS.VERSION) + public static class QSImplNoDeps { + } + + @Requires(target = QS.class, version = QS.VERSION) + @Requires(target = Callback.class, version = Callback.VERSION) + @Requires(target = HeightListener.class, version = HeightListener.VERSION) + @Requires(target = DetailAdapter.class, version = DetailAdapter.VERSION) + public static class QSImpl { + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 4146cb81873d..70c7d3eda7c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -18,26 +18,32 @@ package com.android.systemui.qs.external; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.mock; + import android.content.ComponentName; -import android.content.Context; import android.os.Looper; import android.service.quicksettings.Tile; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; + +import com.android.systemui.SysUIRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.phone.QSTileHost; -import com.android.systemui.statusbar.policy.DataSaverController; -import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.NetworkController; -import java.util.ArrayList; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.utils.TestableLooper; +import com.android.systemui.utils.TestableLooper.RunWithLooper; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import java.util.ArrayList; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(SysUIRunner.class) +@RunWithLooper(setAsMainLooper = true) public class TileServicesTest extends SysuiTestCase { private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2; @@ -46,16 +52,24 @@ public class TileServicesTest extends SysuiTestCase { @Before public void setUp() throws Exception { + TestableLooper.get(this).setAsMainLooper(); mManagers = new ArrayList<>(); - QSTileHost host = new QSTileHost(mContext, null, null); + QSTileHost host = new QSTileHost(mContext, null, + mock(StatusBarIconController.class)); mTileService = new TestTileServices(host, Looper.getMainLooper()); } + @After + public void tearDown() throws Exception { + mTileService.getHost().destroy(); + TestableLooper.get(this).processAllMessages(); + } + @Test public void testRecalculateBindAllowance() { // Add some fake tiles. for (int i = 0; i < NUM_FAKES; i++) { - mTileService.getTileWrapper(Mockito.mock(CustomTile.class)); + mTileService.getTileWrapper(mock(CustomTile.class)); } assertEquals(NUM_FAKES, mManagers.size()); @@ -91,7 +105,7 @@ public class TileServicesTest extends SysuiTestCase { @Test public void testCalcFew() { for (int i = 0; i < TileServices.DEFAULT_MAX_BOUND - 1; i++) { - mTileService.getTileWrapper(Mockito.mock(CustomTile.class)); + mTileService.getTileWrapper(mock(CustomTile.class)); } mTileService.recalculateBindAllowance(); @@ -115,7 +129,7 @@ public class TileServicesTest extends SysuiTestCase { @Override protected TileServiceManager onCreateTileService(ComponentName component, Tile qsTile) { - TileServiceManager manager = Mockito.mock(TileServiceManager.class); + TileServiceManager manager = mock(TileServiceManager.class); mManagers.add(manager); return manager; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java index d1abccaa6e76..59a93618cdb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java @@ -31,13 +31,7 @@ public class FakePluginManager extends PluginManager { @Override public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version) { - mLeakChecker.addCallback(listener); - } - - @Override - public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version, boolean allowMultiple) { + Class cls, boolean allowMultiple) { mLeakChecker.addCallback(listener); } @@ -47,7 +41,7 @@ public class FakePluginManager extends PluginManager { } @Override - public <T extends Plugin> T getOneShotPlugin(String action, int version) { + public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { return null; } } diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 653e80af83b2..8d2f0c3145bf 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -41,6 +41,9 @@ message MetricsEvent { // The view or control was dismissed. TYPE_DISMISS = 5; + + // The view or control was updated. + TYPE_UPDATE = 6; } // Known visual elements: views or controls. @@ -3397,6 +3400,35 @@ message MetricsEvent { // ACTION: A tile in Settings information architecture is clicked ACTION_SETTINGS_TILE_CLICK = 830; + // OPEN: Notification unsnoozed. CLOSE: Notification snoozed. UPDATE: snoozed notification + // updated + // CATEGORY: NOTIFICATION + // OS: O + NOTIFICATION_SNOOZED = 831; + + // Tagged data for NOTIFICATION_SNOOZED. TRUE: snoozed until context, FALSE: snoozed for time. + // OS: O + NOTIFICATION_SNOOZED_CRITERIA = 832; + + // FIELD - The context (source) from which an action is performed + FIELD_CONTEXT = 833; + + // ACTION: Settings advanced button is expanded + ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND = 834; + + // ACTION: Logs the number of times the saved network evaluator was used to + // recommend a wifi network + WIFI_NETWORK_RECOMMENDATION_SAVED_NETWORK_EVALUATOR = 835; + + // ACTION: Logs the number of times the recommended network evaluator was + // used to recommend a wifi network + WIFI_NETWORK_RECOMMENDATION_RECOMMENDED_NETWORK_EVALUATOR = 836; + + // ACTION: Logs the number of times a recommended network was resulted in a + // successful connection + // VALUE: true if the connection was successful, false if the connection failed + WIFI_NETWORK_RECOMMENDATION_CONNECTION_SUCCESS = 837; + // ---- End O Constants, all O constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 719a64e32241..fe0b8401a6cd 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -4166,7 +4166,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } ensureRequestableCapabilities(networkCapabilities); - if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) { + if (timeoutMs < 0) { throw new IllegalArgumentException("Bad timeout specified"); } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index dc987fac97e5..e6f6e5764d95 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -901,7 +901,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mPackagesToMonitorComponentChange.add(packageName); } - private boolean isChangingPackagesOfCurrentUser() { + @GuardedBy("mMethodMap") + private boolean isChangingPackagesOfCurrentUserLocked() { final int userId = getChangingUserId(); final boolean retval = userId == mSettings.getCurrentUserId(); if (DEBUG) { @@ -914,10 +915,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { - if (!isChangingPackagesOfCurrentUser()) { - return false; - } synchronized (mMethodMap) { + if (!isChangingPackagesOfCurrentUserLocked()) { + return false; + } String curInputMethodId = mSettings.getSelectedInputMethod(); final int N = mMethodList.size(); if (curInputMethodId != null) { @@ -951,10 +952,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onSomePackagesChanged() { - if (!isChangingPackagesOfCurrentUser()) { - return; - } synchronized (mMethodMap) { + if (!isChangingPackagesOfCurrentUserLocked()) { + return; + } InputMethodInfo curIm = null; String curInputMethodId = mSettings.getSelectedInputMethod(); final int N = mMethodList.size(); diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 42eb958bba0b..ef7780c79126 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -19,6 +19,7 @@ package com.android.server; import android.app.ActivityManager; import android.annotation.NonNull; import android.content.pm.PackageManagerInternal; +import android.util.ArraySet; import com.android.internal.content.PackageMonitor; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -135,8 +136,6 @@ public class LocationManagerService extends ILocationManager.Stub { private static final String FUSED_LOCATION_SERVICE_ACTION = "com.android.location.service.FusedLocationProvider"; - private static final String GMSCORE_PACKAGE = "com.android.google.gms"; - private static final int MSG_LOCATION_CHANGED = 1; private static final long NANOS_PER_MILLI = 1000000L; @@ -224,7 +223,7 @@ public class LocationManagerService extends ILocationManager.Stub { private final ArrayList<LocationProviderProxy> mProxyProviders = new ArrayList<>(); - private String[] mBackgroundThrottlePackageWhitelist = new String[]{}; + private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>(); // current active user on the device - other users are denied location data private int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -345,6 +344,8 @@ public class LocationManagerService extends ILocationManager.Stub { mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); updateUserProfiles(mCurrentUserId); + updateThrottlingWhitelistLocked(); + // prepare providers loadProvidersLocked(); updateProvidersLocked(); @@ -380,14 +381,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - String setting = Settings.Global.getString( - mContext.getContentResolver(), - Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST); - if (setting == null) { - setting = ""; - } - - mBackgroundThrottlePackageWhitelist = setting.split(","); + updateThrottlingWhitelistLocked(); updateProvidersLocked(); } } @@ -1747,12 +1741,27 @@ public class LocationManagerService extends ILocationManager.Stub { p.setRequest(providerRequest, worksource); } + private void updateThrottlingWhitelistLocked() { + String setting = Settings.Global.getString( + mContext.getContentResolver(), + Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST); + if (setting == null) { + setting = ""; + } + + mBackgroundThrottlePackageWhitelist.clear(); + mBackgroundThrottlePackageWhitelist.addAll( + SystemConfig.getInstance().getAllowUnthrottledLocation()); + mBackgroundThrottlePackageWhitelist.addAll( + Arrays.asList(setting.split(","))); + } + private boolean isThrottlingExemptLocked(Receiver receiver) { if (receiver.mUid == Process.SYSTEM_UID) { return true; } - if (receiver.mPackageName.equals(GMSCORE_PACKAGE)) { + if (mBackgroundThrottlePackageWhitelist.contains(receiver.mPackageName)) { return true; } @@ -1762,12 +1771,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - for (String whitelistedPackage : mBackgroundThrottlePackageWhitelist) { - if (receiver.mPackageName.equals(whitelistedPackage)) { - return true; - } - } - return false; } @@ -2999,6 +3002,13 @@ public class LocationManagerService extends ILocationManager.Stub { } } + if (!mBackgroundThrottlePackageWhitelist.isEmpty()) { + pw.println(" Throttling Whitelisted Packages:"); + for (String packageName : mBackgroundThrottlePackageWhitelist) { + pw.println(" " + packageName); + } + } + pw.append(" fudger: "); mLocationFudger.dump(fd, pw, args); diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index a073d8ea9c5b..4a4453046295 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -20,6 +20,8 @@ import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.READ_CONTACTS; import static android.content.Context.KEYGUARD_SERVICE; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; +import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY; +import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -61,6 +63,7 @@ import android.os.storage.StorageManager; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; +import android.security.GateKeeper; import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyProperties; @@ -79,6 +82,8 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.server.LockSettingsStorage.CredentialHash; +import com.android.server.SyntheticPasswordManager.AuthenticationResult; +import com.android.server.SyntheticPasswordManager.AuthenticationToken; import libcore.util.HexEncoding; @@ -86,6 +91,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -145,12 +151,14 @@ public class LockSettingsService extends ILockSettings.Stub { private boolean mFirstCallToVold; protected IGateKeeperService mGateKeeperService; + private SyntheticPasswordManager mSpManager; + /** * The UIDs that are used for system credential storage in keystore. */ private static final int[] SYSTEM_CREDENTIAL_UIDS = { Process.WIFI_UID, Process.VPN_UID, - Process.ROOT_UID, Process.SYSTEM_UID }; + Process.ROOT_UID }; // This class manages life cycle events for encrypted users on File Based Encryption (FBE) // devices. The most basic of these is to show/hide notifications about missing features until @@ -335,6 +343,14 @@ public class LockSettingsService extends ILockSettings.Stub { } return null; } + + public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) { + return new SyntheticPasswordManager(storage); + } + + public int binderGetCallingUid() { + return Binder.getCallingUid(); + } } public LockSettingsService(Context context) { @@ -365,6 +381,8 @@ public class LockSettingsService extends ILockSettings.Stub { mUserManager = injector.getUserManager(); mStrongAuthTracker = injector.getStrongAuthTracker(); mStrongAuthTracker.register(mStrongAuth); + + mSpManager = injector.getSyntheticPasswordManager(mStorage); } /** @@ -801,17 +819,42 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public boolean havePassword(int userId) throws RemoteException { + synchronized (mSpManager) { + if (isSyntheticPasswordBasedCredentialLocked(userId)) { + long handle = getSyntheticPasswordHandleLocked(userId); + return mSpManager.getCredentialType(handle, userId) == + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; + } + } // Do we need a permissions check here? return mStorage.hasPassword(userId); } @Override public boolean havePattern(int userId) throws RemoteException { + synchronized (mSpManager) { + if (isSyntheticPasswordBasedCredentialLocked(userId)) { + long handle = getSyntheticPasswordHandleLocked(userId); + return mSpManager.getCredentialType(handle, userId) == + LockPatternUtils.CREDENTIAL_TYPE_PATTERN; + } + } // Do we need a permissions check here? return mStorage.hasPattern(userId); } private boolean isUserSecure(int userId) { + synchronized (mSpManager) { + try { + if (isSyntheticPasswordBasedCredentialLocked(userId)) { + long handle = getSyntheticPasswordHandleLocked(userId); + return mSpManager.getCredentialType(handle, userId) != + LockPatternUtils.CREDENTIAL_TYPE_NONE; + } + } catch (RemoteException e) { + // fall through + } + } return mStorage.hasCredential(userId); } @@ -1021,6 +1064,13 @@ public class LockSettingsService extends ILockSettings.Stub { private void setLockCredentialInternal(String credential, int credentialType, String savedCredential, int userId) throws RemoteException { + synchronized (mSpManager) { + if (isSyntheticPasswordBasedCredentialLocked(userId)) { + spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential, + userId); + return; + } + } if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { if (credential != null) { Slog.wtf(TAG, "CredentialType is none, but credential is non-null."); @@ -1061,7 +1111,16 @@ public class LockSettingsService extends ILockSettings.Stub { savedCredential = null; } } - + synchronized (mSpManager) { + if (shouldMigrateToSyntheticPasswordLocked(userId)) { + initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential, + currentHandle.type, userId); + spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential, + userId); + return; + } + } + if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId); byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential, userId); if (enrolledHandle != null) { @@ -1189,6 +1248,11 @@ public class LockSettingsService extends ILockSettings.Stub { return hash; } + private void setAuthlessUserKeyProtection(int userId, byte[] key) throws RemoteException { + if (DEBUG) Slog.d(TAG, "setAuthlessUserKeyProtectiond: user=" + userId); + addUserKeyAuth(userId, null, key); + } + private void setUserKeyProtection(int userId, String credential, VerifyCredentialResponse vcr) throws RemoteException { if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId); @@ -1320,7 +1384,16 @@ public class LockSettingsService extends ILockSettings.Stub { if (TextUtils.isEmpty(credential)) { throw new IllegalArgumentException("Credential can't be null or empty"); } - + synchronized (mSpManager) { + if (isSyntheticPasswordBasedCredentialLocked(userId)) { + VerifyCredentialResponse response = spBasedDoVerifyCredentialLocked(credential, + credentialType, hasChallenge, challenge, userId, progressCallback); + if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { + mStrongAuth.reportSuccessfulStrongAuthUnlock(userId); + } + return response; + } + } CredentialHash storedHash = mStorage.readCredentialHash(userId); if (storedHash.type != credentialType) { Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??" @@ -1456,8 +1529,8 @@ public class LockSettingsService extends ILockSettings.Stub { notifyActivePasswordMetricsAvailable(credential, userId); unlockKeystore(credential, userId); - Slog.i(TAG, "Unlocking user " + userId + - " with token length " + response.getPayload().length); + Slog.i(TAG, "Unlocking user " + userId + " with token length " + + response.getPayload().length); unlockUser(userId, response.getPayload(), secretFromCredential(credential)); if (isManagedProfileWithSeparatedLock(userId)) { @@ -1467,6 +1540,15 @@ public class LockSettingsService extends ILockSettings.Stub { } if (shouldReEnroll) { setLockCredentialInternal(credential, storedHash.type, credential, userId); + } else { + // Now that we've cleared of all required GK migration, let's do the final + // migration to synthetic password. + synchronized (mSpManager) { + if (shouldMigrateToSyntheticPasswordLocked(userId)) { + initializeSyntheticPasswordLocked(storedHash.hash, credential, + storedHash.type, userId); + } + } } } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { @@ -1697,7 +1779,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private synchronized IGateKeeperService getGateKeeperService() + protected synchronized IGateKeeperService getGateKeeperService() throws RemoteException { if (mGateKeeperService != null) { return mGateKeeperService; @@ -1713,4 +1795,431 @@ public class LockSettingsService extends ILockSettings.Stub { Slog.e(TAG, "Unable to acquire GateKeeperService"); return null; } + + /** + * Precondition: vold and keystore unlocked. + * + * Create new synthetic password, set up synthetic password blob protected by the supplied + * user credential, and make the newly-created SP blob active. + * + * The invariant under a synthetic password is: + * 1. If user credential exists, then both vold and keystore and protected with keys derived + * from the synthetic password. + * 2. If user credential does not exist, vold and keystore protection are cleared. This is to + * make it consistent with current behaviour. It also allows ActivityManager to call + * unlockUser() with empty secret. + * 3. Once a user is migrated to have synthetic password, its value will never change, no matter + * whether the user changes his lockscreen PIN or clear/reset it. When the user clears its + * lockscreen PIN, we still maintain the existing synthetic password in a password blob + * protected by a default PIN. The only exception is when the DPC performs an untrusted + * credential change, in which case we have no way to derive the existing synthetic password + * and has to create a new one. + * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user + * clears/re-creates his lockscreen PIN. + * + * + * Different cases of calling this method: + * 1. credentialHash != null + * This implies credential != null, a new SP blob will be provisioned, and existing SID + * migrated to associate with the new SP. + * This happens during a normal migration case when the user currently has password. + * + * 2. credentialhash == null and credential == null + * A new SP blob and a new SID will be created, while the user has no credentials. + * This can happens when we are activating an escrow token on a unsecured device, during + * which we want to create the SP structure with an empty user credential. + * + * 3. credentialhash == null and credential != null + * This is the untrusted credential reset, OR the user sets a new lockscreen password + * FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created + */ + private AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash, + String credential, int credentialType, int userId) throws RemoteException { + Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId); + AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(), + credentialHash, credential, userId); + if (auth == null) { + Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token"); + return null; + } + long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(), + credential, credentialType, auth, userId); + if (credential != null) { + if (credentialHash == null) { + // Since when initializing SP, we didn't provide an existing password handle + // for it to migrate SID, we need to create a new SID for the user. + mSpManager.newSidForUser(getGateKeeperService(), auth, userId); + } + mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId); + setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey()); + setKeystorePassword(auth.deriveKeyStorePassword(), userId); + } else { + clearUserKeyProtection(userId); + setKeystorePassword(null, userId); + getGateKeeperService().clearSecureUserId(userId); + } + fixateNewestUserKeyAuth(userId); + setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, handle, userId); + return auth; + } + + private long getSyntheticPasswordHandleLocked(int userId) { + try { + return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId); + } catch (RemoteException e) { + return SyntheticPasswordManager.DEFAULT_HANDLE; + } + } + + private boolean isSyntheticPasswordBasedCredentialLocked(int userId) throws RemoteException { + long handle = getSyntheticPasswordHandleLocked(userId); + // This is a global setting + long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM); + return enabled != 0 && handle != SyntheticPasswordManager.DEFAULT_HANDLE; + } + + private boolean shouldMigrateToSyntheticPasswordLocked(int userId) throws RemoteException { + long handle = getSyntheticPasswordHandleLocked(userId); + // This is a global setting + long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM); + return enabled != 0 && handle == SyntheticPasswordManager.DEFAULT_HANDLE; + } + + private void enableSyntheticPasswordLocked() throws RemoteException { + setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM); + } + + private VerifyCredentialResponse spBasedDoVerifyCredentialLocked(String userCredential, int + credentialType, boolean hasChallenge, long challenge, int userId, + ICheckCredentialProgressCallback progressCallback) throws RemoteException { + if (DEBUG) Slog.d(TAG, "spBasedDoVerifyCredentialLocked: user=" + userId); + if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { + userCredential = null; + } + long handle = getSyntheticPasswordHandleLocked(userId); + AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword( + getGateKeeperService(), handle, userCredential, userId); + + VerifyCredentialResponse response = authResult.gkResponse; + if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { + // credential has matched + // perform verifyChallenge with synthetic password which generates the real auth + // token for the current user + response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken, + challenge, userId); + if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { + Slog.wtf(TAG, "verifyChallenge with SP failed."); + return VerifyCredentialResponse.ERROR; + } + if (progressCallback != null) { + progressCallback.onCredentialVerified(); + } + notifyActivePasswordMetricsAvailable(userCredential, userId); + unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId); + + final byte[] secret = authResult.authToken.deriveDiskEncryptionKey(); + Slog.i(TAG, "Unlocking user " + userId + " with secret only, length " + secret.length); + unlockUser(userId, null, secret); + + if (isManagedProfileWithSeparatedLock(userId)) { + TrustManager trustManager = + (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); + trustManager.setDeviceLockedForUser(userId, false); + } + activateEscrowTokens(authResult.authToken, userId); + } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { + if (response.getTimeout() > 0) { + requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); + } + } + + return response; + } + + /** + * Change the user's lockscreen password by creating a new SP blob and update the handle, based + * on an existing authentication token. Even though a new SP blob is created, the underlying + * synthetic password is never changed. + * + * When clearing credential, we keep the SP unchanged, but clear its password handle so its + * SID is gone. We also clear password from (software-based) keystore and vold, which will be + * added back when new password is set in future. + */ + private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType, + AuthenticationToken auth, int userId) throws RemoteException { + if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId); + long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(), + credential, credentialType, auth, userId); + final Map<Integer, String> profilePasswords; + if (credential != null) { + // // not needed by synchronizeUnifiedWorkChallengeForProfiles() + profilePasswords = null; + + if (mSpManager.hasSidForUser(userId)) { + // We are changing password of a secured device, nothing more needed as + // createPasswordBasedSyntheticPassword has already taken care of maintaining + // the password handle and SID unchanged. + + //refresh auth token + mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId); + } else { + // A new password is set on a previously-unsecured device, we need to generate + // a new SID, and re-add keys to vold and keystore. + mSpManager.newSidForUser(getGateKeeperService(), auth, userId); + mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId); + setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey()); + fixateNewestUserKeyAuth(userId); + setKeystorePassword(auth.deriveKeyStorePassword(), userId); + } + } else { + // Cache all profile password if they use unified work challenge. This will later be + // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles() + profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId); + + // we are clearing password of a secured device, so need to nuke SID as well. + mSpManager.clearSidForUser(userId); + getGateKeeperService().clearSecureUserId(userId); + // Clear key from vold so ActivityManager can just unlock the user with empty secret + // during boot. + clearUserKeyProtection(userId); + fixateNewestUserKeyAuth(userId); + setKeystorePassword(null, userId); + } + setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, newHandle, userId); + synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords); + return newHandle; + } + + private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType, + String savedCredential, int userId) throws RemoteException { + if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId); + if (isManagedProfileWithUnifiedLock(userId)) { + // get credential from keystore when managed profile has unified lock + try { + savedCredential = getDecryptedPasswordForTiedProfile(userId); + } catch (FileNotFoundException e) { + Slog.i(TAG, "Child profile key not found"); + } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException + | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException | CertificateException | IOException e) { + Slog.e(TAG, "Failed to decrypt child profile key", e); + } + } + long handle = getSyntheticPasswordHandleLocked(userId); + AuthenticationToken auth = mSpManager.unwrapPasswordBasedSyntheticPassword( + getGateKeeperService(), handle, savedCredential, userId).authToken; + if (auth != null) { + // We are performing a trusted credential change i.e. a correct existing credential + // is provided + setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, userId); + mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId); + } else { + // We are performing an untrusted credential change i.e. by DevicePolicyManager. + // So provision a new SP and SID. This would invalidate existing escrow tokens. + // Still support this for now but this flow will be removed in the next release. + + Slog.w(TAG, "Untrusted credential change invoked"); + initializeSyntheticPasswordLocked(null, credential, credentialType, userId); + synchronizeUnifiedWorkChallengeForProfiles(userId, null); + mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId); + } + notifyActivePasswordMetricsAvailable(credential, userId); + + } + + @Override + public long addEscrowToken(byte[] token, int userId) throws RemoteException { + ensureCallerSystemUid(); + if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId); + synchronized (mSpManager) { + enableSyntheticPasswordLocked(); + // Migrate to synthetic password based credentials if ther user has no password, + // the token can then be activated immediately. + AuthenticationToken auth = null; + if (!isUserSecure(userId)) { + if (shouldMigrateToSyntheticPasswordLocked(userId)) { + auth = initializeSyntheticPasswordLocked(null, null, + LockPatternUtils.CREDENTIAL_TYPE_NONE, userId); + } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ { + long pwdHandle = getSyntheticPasswordHandleLocked(userId); + auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(), + pwdHandle, null, userId).authToken; + } + } + disableEscrowTokenOnNonManagedDevicesIfNeeded(userId); + if (!mSpManager.hasEscrowData(userId)) { + throw new SecurityException("Escrow token is disabled on the current user"); + } + long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId); + if (auth != null) { + mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId); + } + return handle; + } + } + + private void activateEscrowTokens(AuthenticationToken auth, int userId) throws RemoteException { + if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId); + synchronized (mSpManager) { + for (long handle : mSpManager.getPendingTokensForUser(userId)) { + Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId)); + mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId); + } + } + } + + @Override + public boolean isEscrowTokenActive(long handle, int userId) throws RemoteException { + ensureCallerSystemUid(); + synchronized (mSpManager) { + return mSpManager.existsHandle(handle, userId); + } + } + + @Override + public boolean removeEscrowToken(long handle, int userId) throws RemoteException { + ensureCallerSystemUid(); + synchronized (mSpManager) { + if (handle == getSyntheticPasswordHandleLocked(userId)) { + Slog.w(TAG, "Cannot remove password handle"); + return false; + } + if (mSpManager.removePendingToken(handle, userId)) { + return true; + } + if (mSpManager.existsHandle(handle, userId)) { + mSpManager.destroyTokenBasedSyntheticPassword(handle, userId); + return true; + } else { + return false; + } + } + } + + @Override + public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, + byte[] token, int userId) throws RemoteException { + ensureCallerSystemUid(); + boolean result; + synchronized (mSpManager) { + if (!mSpManager.hasEscrowData(userId)) { + throw new SecurityException("Escrow token is disabled on the current user"); + } + result = setLockCredentialWithTokenInternal(credential, type, tokenHandle, token, + userId); + } + if (result) { + synchronized (mSeparateChallengeLock) { + setSeparateProfileChallengeEnabled(userId, true, null); + } + notifyPasswordChanged(userId); + } + return result; + } + + private boolean setLockCredentialWithTokenInternal(String credential, int type, + long tokenHandle, byte[] token, int userId) throws RemoteException { + synchronized (mSpManager) { + AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword( + getGateKeeperService(), tokenHandle, token, userId); + if (result.authToken == null) { + Slog.w(TAG, "Invalid escrow token supplied"); + return false; + } + long oldHandle = getSyntheticPasswordHandleLocked(userId); + setLockCredentialWithAuthTokenLocked(credential, type, result.authToken, userId); + mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId); + return true; + } + } + + @Override + public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) + throws RemoteException { + ensureCallerSystemUid(); + AuthenticationResult authResult; + synchronized (mSpManager) { + if (!mSpManager.hasEscrowData(userId)) { + throw new SecurityException("Escrow token is disabled on the current user"); + } + authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), + tokenHandle, token, userId); + if (authResult.authToken == null) { + Slog.w(TAG, "Invalid escrow token supplied"); + return; + } + } + unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey()); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args){ + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + + pw.println("Permission Denial: can't dump LockSettingsService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (this) { + pw.println("Current lock settings service state:"); + pw.println(String.format("SP Enabled = %b", + mLockPatternUtils.isSyntheticPasswordEnabled())); + + List<UserInfo> users = mUserManager.getUsers(); + for (int user = 0; user < users.size(); user++) { + final int userId = users.get(user).id; + pw.println(" User " + userId); + pw.println(String.format(" SP Handle = %x", + getSyntheticPasswordHandleLocked(userId))); + try { + pw.println(String.format(" SID = %x", + getGateKeeperService().getSecureUserId(userId))); + } catch (RemoteException e) { + // ignore. + } + } + } + } + + private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) { + long ident = Binder.clearCallingIdentity(); + try { + // Managed profile should have escrow enabled + if (mUserManager.getUserInfo(userId).isManagedProfile()) { + return; + } + DevicePolicyManager dpm = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + // Devices with Device Owner should have escrow enabled on all users. + if (dpm.getDeviceOwnerComponentOnAnyUser() != null) { + return; + } + // If the device is yet to be provisioned (still in SUW), there is still + // a chance that Device Owner will be set on the device later, so postpone + // disabling escrow token for now. + if (!dpm.isDeviceProvisioned()) { + return; + } + // Disable escrow token permanently on all other device/user types. + Slog.i(TAG, "Disabling escrow token on user " + userId); + if (isSyntheticPasswordBasedCredentialLocked(userId)) { + mSpManager.destroyEscrowData(userId); + } + } catch (RemoteException e) { + Slog.e(TAG, "disableEscrowTokenOnNonManagedDevices", e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void ensureCallerSystemUid() throws SecurityException { + final int callingUid = mInjector.binderGetCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + throw new SecurityException("Only system can call this API."); + } + } } diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java index 1ab53035a3ee..91bd98eb5f5f 100644 --- a/services/core/java/com/android/server/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/LockSettingsShellCommand.java @@ -22,12 +22,9 @@ import static com.android.internal.widget.LockPatternUtils.stringToPattern; import android.app.ActivityManager; import android.content.Context; -import android.os.Binder; -import android.os.Process; import android.os.RemoteException; import android.os.ShellCommand; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.RequestThrottledException; @@ -37,6 +34,7 @@ class LockSettingsShellCommand extends ShellCommand { private static final String COMMAND_SET_PIN = "set-pin"; private static final String COMMAND_SET_PASSWORD = "set-password"; private static final String COMMAND_CLEAR = "clear"; + private static final String COMMAND_SP = "sp"; private int mCurrentUserId; private final LockPatternUtils mLockPatternUtils; @@ -71,6 +69,9 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_CLEAR: runClear(); break; + case COMMAND_SP: + runEnableSp(); + break; default: getErrPrintWriter().println("Unknown command: " + cmd); break; @@ -92,6 +93,8 @@ class LockSettingsShellCommand extends ShellCommand { while ((opt = getNextOption()) != null) { if ("--old".equals(opt)) { mOld = getNextArgRequired(); + } else if ("--user".equals(opt)) { + mCurrentUserId = Integer.parseInt(getNextArgRequired()); } else { getErrPrintWriter().println("Unknown option: " + opt); throw new IllegalArgumentException(); @@ -100,6 +103,15 @@ class LockSettingsShellCommand extends ShellCommand { mNew = getNextArg(); } + private void runEnableSp() { + if (mNew != null) { + mLockPatternUtils.enableSyntheticPassword(); + getOutPrintWriter().println("Synthetic password enabled"); + } + getOutPrintWriter().println(String.format("SP Enabled = %b", + mLockPatternUtils.isSyntheticPasswordEnabled())); + } + private void runSetPattern() throws RemoteException { mLockPatternUtils.saveLockPattern(stringToPattern(mNew), mOld, mCurrentUserId); getOutPrintWriter().println("Pattern set to '" + mNew + "'"); diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java index 385b1cf4e448..f5bae7c14d97 100644 --- a/services/core/java/com/android/server/LockSettingsStorage.java +++ b/services/core/java/com/android/server/LockSettingsStorage.java @@ -66,6 +66,8 @@ class LockSettingsStorage { private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key"; private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key"; + private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/"; + private static final Object DEFAULT = new Object(); private final DatabaseHelper mOpenHelper; @@ -412,8 +414,7 @@ class LockSettingsStorage { } private String getLockCredentialFilePathForUser(int userId, String basename) { - String dataSystemDirectory = - android.os.Environment.getDataDirectory().getAbsolutePath() + + String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() + SYSTEM_DIRECTORY; if (userId == 0) { // Leave it in the same place for user 0 @@ -423,6 +424,40 @@ class LockSettingsStorage { } } + public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) { + writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data); + } + + public byte[] readSyntheticPasswordState(int userId, long handle, String name) { + return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name)); + } + + public void deleteSyntheticPasswordState(int userId, long handle, String name, boolean secure) { + String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name); + File file = new File(path); + if (file.exists()) { + //TODO: (b/34600579) invoke secdiscardable + file.delete(); + mCache.putFile(path, null); + } + } + + @VisibleForTesting + protected File getSyntheticPasswordDirectoryForUser(int userId) { + return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY); + } + + @VisibleForTesting + protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle, + String name) { + File baseDir = getSyntheticPasswordDirectoryForUser(userId); + String baseName = String.format("%016x.%s", handle, name); + if (!baseDir.exists()) { + baseDir.mkdir(); + } + return new File(baseDir, baseName).getAbsolutePath(); + } + public void removeUser(int userId) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); @@ -446,15 +481,20 @@ class LockSettingsStorage { } } } else { - // Manged profile + // Managed profile removeChildProfileLock(userId); } + File spStateDir = getSyntheticPasswordDirectoryForUser(userId); try { db.beginTransaction(); db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); db.setTransactionSuccessful(); mCache.removeUser(userId); + // The directory itself will be deleted as part of user deletion operation by the + // framework, so only need to purge cache here. + //TODO: (b/34600579) invoke secdiscardable + mCache.purgePath(spStateDir.getAbsolutePath()); } finally { db.endTransaction(); } @@ -619,6 +659,16 @@ class LockSettingsStorage { mVersion++; } + synchronized void purgePath(String path) { + for (int i = mCache.size() - 1; i >= 0; i--) { + CacheKey entry = mCache.keyAt(i); + if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) { + mCache.removeAt(i); + } + } + mVersion++; + } + synchronized void clear() { mCache.clear(); mVersion++; diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index 3e8985295c66..b33538cbcb07 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -603,7 +603,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { mScanResultKeys = new ArraySet<>(size); for (int i = 0; i < size; i++) { ScanResult scanResult = scanResults.get(i); - mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult)); + NetworkKey key = NetworkKey.createFromScanResult(scanResult); + if (key != null) { + mScanResultKeys.add(key); + } } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 32b7e4d1b5b4..041597168eaa 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2922,10 +2922,10 @@ class StorageManagerService extends IStorageManager.Stub waitForReady(); if (StorageManager.isFileEncryptedNativeOrEmulated()) { - // When a user has secure lock screen, require a challenge token to - // actually unlock. This check is mostly in place for emulation mode. - if (mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(token)) { - throw new IllegalStateException("Token required to unlock secure user " + userId); + // When a user has secure lock screen, require secret to actually unlock. + // This check is mostly in place for emulation mode. + if (mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(secret)) { + throw new IllegalStateException("Secret required to unlock secure user " + userId); } try { diff --git a/services/core/java/com/android/server/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/SyntheticPasswordCrypto.java new file mode 100644 index 000000000000..12d91c57cdad --- /dev/null +++ b/services/core/java/com/android/server/SyntheticPasswordCrypto.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyProtection; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class SyntheticPasswordCrypto { + private static final int PROFILE_KEY_IV_SIZE = 12; + private static final int AES_KEY_LENGTH = 32; // 256-bit AES key + private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes(); + // Time between the user credential is verified with GK and the decryption of synthetic password + // under the auth-bound key. This should always happen one after the other, but give it 15 + // seconds just to be sure. + private static final int USER_AUTHENTICATION_VALIDITY = 15; + + private static byte[] decrypt(SecretKey key, byte[] blob) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + if (blob == null) { + return null; + } + byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE); + byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length); + Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); + cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv)); + return cipher.doFinal(ciphertext); + } + + private static byte[] encrypt(SecretKey key, byte[] blob) + throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + if (blob == null) { + return null; + } + Cipher cipher = Cipher.getInstance( + KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + + KeyProperties.ENCRYPTION_PADDING_NONE); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] ciphertext = cipher.doFinal(blob); + byte[] iv = cipher.getIV(); + if (iv.length != PROFILE_KEY_IV_SIZE) { + throw new RuntimeException("Invalid iv length: " + iv.length); + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(iv); + outputStream.write(ciphertext); + return outputStream.toByteArray(); + } + + public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) { + byte[] keyHash = personalisedHash(personalisation, keyBytes); + SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH), + KeyProperties.KEY_ALGORITHM_AES); + try { + return encrypt(key, message); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | IllegalBlockSizeException | BadPaddingException | IOException e) { + e.printStackTrace(); + return null; + } + } + + public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) { + byte[] keyHash = personalisedHash(personalisation, keyBytes); + SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH), + KeyProperties.KEY_ALGORITHM_AES); + try { + return decrypt(key, ciphertext); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | IllegalBlockSizeException | BadPaddingException + | InvalidAlgorithmParameterException e) { + e.printStackTrace(); + return null; + } + } + + public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) { + try { + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + + SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null); + byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob); + return decrypt(decryptionKey, intermediate); + } catch (CertificateException | IOException | BadPaddingException + | IllegalBlockSizeException + | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException + | InvalidKeyException | UnrecoverableKeyException + | InvalidAlgorithmParameterException e) { + e.printStackTrace(); + throw new RuntimeException("Failed to decrypt blob", e); + } + } + + public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); + keyGenerator.init(new SecureRandom()); + SecretKey secretKey = keyGenerator.generateKey(); + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE); + if (sid != 0) { + builder.setUserAuthenticationRequired(true) + .setBoundToSpecificSecureUserId(sid) + .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY); + } + keyStore.setEntry(keyAlias, + new KeyStore.SecretKeyEntry(secretKey), + builder.build()); + byte[] intermediate = encrypt(secretKey, data); + return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate); + + } catch (CertificateException | IOException | BadPaddingException + | IllegalBlockSizeException + | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException + | InvalidKeyException e) { + e.printStackTrace(); + throw new RuntimeException("Failed to encrypt blob", e); + } + } + + public static void destroyBlobKey(String keyAlias) { + KeyStore keyStore; + try { + keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + keyStore.deleteEntry(keyAlias); + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException + | IOException e) { + e.printStackTrace(); + } + } + + protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) { + try { + final int PADDING_LENGTH = 128; + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + if (personalisation.length > PADDING_LENGTH) { + throw new RuntimeException("Personalisation too long"); + } + // Personalize the hash + // Pad it to the block size of the hash function + personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH); + digest.update(personalisation); + for (byte[] data : message) { + digest.update(data); + } + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e); + } + } +} diff --git a/services/core/java/com/android/server/SyntheticPasswordManager.java b/services/core/java/com/android/server/SyntheticPasswordManager.java new file mode 100644 index 000000000000..62678801a9a2 --- /dev/null +++ b/services/core/java/com/android/server/SyntheticPasswordManager.java @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import android.service.gatekeeper.GateKeeperResponse; +import android.service.gatekeeper.IGateKeeperService; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.VerifyCredentialResponse; + +import libcore.util.HexEncoding; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + + +/** + * A class that maintains the wrapping of synthetic password by user credentials or escrow tokens. + * It's (mostly) a pure storage for synthetic passwords, providing APIs to creating and destroying + * synthetic password blobs which are wrapped by user credentials or escrow tokens. + * + * Here is the assumptions it makes: + * Each user has one single synthetic password at any time. + * The SP has an associated password handle, which binds to the SID for that user. The password + * handle is persisted by SyntheticPasswordManager internally. + * If the user credential is null, it's treated as if the credential is DEFAULT_PASSWORD + */ +public class SyntheticPasswordManager { + private static final String SP_BLOB_NAME = "spblob"; + private static final String SP_E0_NAME = "e0"; + private static final String SP_P1_NAME = "p1"; + private static final String SP_HANDLE_NAME = "handle"; + private static final String SECDISCARDABLE_NAME = "secdis"; + private static final int SECDISCARDABLE_LENGTH = 16 * 1024; + private static final String PASSWORD_DATA_NAME = "pwd"; + + public static final long DEFAULT_HANDLE = 0; + private static final String DEFAULT_PASSWORD = "default-password"; + + private static final byte SYNTHETIC_PASSWORD_VERSION = 1; + private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0; + private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1; + + // 256-bit synthetic password + private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8; + + private static final int PASSWORD_SCRYPT_N = 13; + private static final int PASSWORD_SCRYPT_R = 3; + private static final int PASSWORD_SCRYPT_P = 1; + private static final int PASSWORD_SALT_LENGTH = 16; + private static final int PASSWORD_TOKEN_LENGTH = 32; + private static final String TAG = "SyntheticPasswordManager"; + + private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes(); + private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes(); + private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes(); + private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes(); + private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes(); + private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes(); + private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes(); + + static class AuthenticationResult { + public AuthenticationToken authToken; + public VerifyCredentialResponse gkResponse; + } + + static class AuthenticationToken { + /* + * Here is the relationship between all three fields: + * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not. + * syntheticPassword = hash(P0 || P1) + * E0 = P0 encrypted under syntheticPassword, stored on disk. + */ + private @Nullable byte[] E0; + private @Nullable byte[] P1; + private @NonNull String syntheticPassword; + + public String deriveKeyStorePassword() { + return bytesToHex(SyntheticPasswordCrypto.personalisedHash( + PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes())); + } + + public byte[] deriveGkPassword() { + return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH, + syntheticPassword.getBytes()); + } + + public byte[] deriveDiskEncryptionKey() { + return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY, + syntheticPassword.getBytes()); + } + + private void initialize(byte[] P0, byte[] P1) { + this.P1 = P1; + this.syntheticPassword = String.valueOf(HexEncoding.encode( + SyntheticPasswordCrypto.personalisedHash( + PERSONALIZATION_SP_SPLIT, P0, P1))); + this.E0 = SyntheticPasswordCrypto.encrypt(this.syntheticPassword.getBytes(), + PERSONALIZATION_E0, P0); + } + + public void recreate(byte[] secret) { + initialize(secret, this.P1); + } + + protected static AuthenticationToken create() { + AuthenticationToken result = new AuthenticationToken(); + result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH), + secureRandom(SYNTHETIC_PASSWORD_LENGTH)); + return result; + } + + public byte[] computeP0() { + if (E0 == null) { + return null; + } + return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0, + E0); + } + } + + static class PasswordData { + byte scryptN; + byte scryptR; + byte scryptP; + public int passwordType; + byte[] salt; + public byte[] passwordHandle; + + public static PasswordData create(int passwordType) { + PasswordData result = new PasswordData(); + result.scryptN = PASSWORD_SCRYPT_N; + result.scryptR = PASSWORD_SCRYPT_R; + result.scryptP = PASSWORD_SCRYPT_P; + result.passwordType = passwordType; + result.salt = secureRandom(PASSWORD_SALT_LENGTH); + return result; + } + + public static PasswordData fromBytes(byte[] data) { + PasswordData result = new PasswordData(); + ByteBuffer buffer = ByteBuffer.allocate(data.length); + buffer.put(data, 0, data.length); + buffer.flip(); + result.passwordType = buffer.getInt(); + result.scryptN = buffer.get(); + result.scryptR = buffer.get(); + result.scryptP = buffer.get(); + int saltLen = buffer.getInt(); + result.salt = new byte[saltLen]; + buffer.get(result.salt); + int handleLen = buffer.getInt(); + result.passwordHandle = new byte[handleLen]; + buffer.get(result.passwordHandle); + return result; + } + + public byte[] toBytes() { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES + + Integer.BYTES + salt.length + Integer.BYTES + passwordHandle.length); + buffer.putInt(passwordType); + buffer.put(scryptN); + buffer.put(scryptR); + buffer.put(scryptP); + buffer.putInt(salt.length); + buffer.put(salt); + buffer.putInt(passwordHandle.length); + buffer.put(passwordHandle); + return buffer.array(); + } + } + + private LockSettingsStorage mStorage; + + public SyntheticPasswordManager(LockSettingsStorage storage) { + mStorage = storage; + } + + + public int getCredentialType(long handle, int userId) { + byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId); + if (passwordData == null) { + Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId); + return LockPatternUtils.CREDENTIAL_TYPE_NONE; + } + return PasswordData.fromBytes(passwordData).passwordType; + } + + /** + * Initializing a new Authentication token, possibly from an existing credential and hash. + * + * The authentication token would bear a randomly-generated synthetic password. + * + * This method has the side effect of rebinding the SID of the given user to the + * newly-generated SP. + * + * If the existing credential hash is non-null, the existing SID mill be migrated so + * the synthetic password in the authentication token will produce the same SID + * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager + * in a per-user data storage. + * + * If the existing credential hash is null, it means the given user should have no SID so + * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case, + * the supplied credential parameter is also ignored. + * + * Also saves the escrow information necessary to re-generate the synthetic password under + * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if + * password escrow should be disabled completely on the given user. + * + */ + public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper, + byte[] hash, String credential, int userId) throws RemoteException { + AuthenticationToken result = AuthenticationToken.create(); + GateKeeperResponse response; + if (hash != null) { + response = gatekeeper.enroll(userId, hash, credential.getBytes(), + result.deriveGkPassword()); + if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { + Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId); + clearSidForUser(userId); + } else { + saveSyntheticPasswordHandle(response.getPayload(), userId); + } + } else { + clearSidForUser(userId); + } + saveEscrowData(result, userId); + return result; + } + + /** + * Enroll a new password handle and SID for the given synthetic password and persist it on disk. + * Used when adding password to previously-unsecured devices. + */ + public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken, + int userId) throws RemoteException { + GateKeeperResponse response = gatekeeper.enroll(userId, null, null, + authToken.deriveGkPassword()); + if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { + Log.e(TAG, "Fail to create new SID for user " + userId); + return; + } + saveSyntheticPasswordHandle(response.getPayload(), userId); + } + + // Nuke the SP handle (and as a result, its SID) for the given user. + public void clearSidForUser(int userId) { + destroyState(SP_HANDLE_NAME, true, DEFAULT_HANDLE, userId); + } + + public boolean hasSidForUser(int userId) { + return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId); + } + + // if null, it means there is no SID associated with the user + // This can happen if the user is migrated to SP but currently + // do not have a lockscreen password. + private byte[] loadSyntheticPasswordHandle(int userId) { + return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId); + } + + private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) { + saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId); + } + + private boolean loadEscrowData(AuthenticationToken authToken, int userId) { + authToken.E0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId); + authToken.P1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId); + return authToken.E0 != null && authToken.P1 != null; + } + + private void saveEscrowData(AuthenticationToken authToken, int userId) { + saveState(SP_E0_NAME, authToken.E0, DEFAULT_HANDLE, userId); + saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId); + } + + public boolean hasEscrowData(int userId) { + return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId) + && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId); + } + + public void destroyEscrowData(int userId) { + destroyState(SP_E0_NAME, true, DEFAULT_HANDLE, userId); + destroyState(SP_P1_NAME, true, DEFAULT_HANDLE, userId); + } + + /** + * Create a new password based SP blob based on the supplied authentication token, such that + * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result + * in the same authentication token. + * + * This method only creates SP blob wrapping around the given synthetic password and does not + * handle logic around SID or SP handle. The caller should separately ensure that the user's SID + * is consistent with the device state by calling other APIs in this class. + * + * @see #newSidForUser + * @see #clearSidForUser + */ + public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper, + String credential, int credentialType, AuthenticationToken authToken, int userId) + throws RemoteException { + if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { + credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE; + credential = DEFAULT_PASSWORD; + } + + long handle = generateHandle(); + PasswordData pwd = PasswordData.create(credentialType); + byte[] pwdToken = computePasswordToken(credential, pwd); + + GateKeeperResponse response = gatekeeper.enroll(fakeUid(userId), null, null, + passwordTokenToGkInput(pwdToken)); + if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { + Log.e(TAG, "Fail to enroll user password when creating SP for user " + userId); + return 0; + } + pwd.passwordHandle = response.getPayload(); + long sid = sidFromPasswordHandle(pwd.passwordHandle); + saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId); + + byte[] applicationId = transformUnderSecdiscardable(pwdToken, + createSecdiscardable(handle, userId)); + createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken, + applicationId, sid, userId); + return handle; + } + + private ArrayMap<Integer, ArrayMap<Long, byte[]>> tokenMap = new ArrayMap<>(); + + public long createTokenBasedSyntheticPassword(byte[] token, int userId) { + long handle = generateHandle(); + byte[] applicationId = transformUnderSecdiscardable(token, + createSecdiscardable(handle, userId)); + if (!tokenMap.containsKey(userId)) { + tokenMap.put(userId, new ArrayMap<>()); + } + tokenMap.get(userId).put(handle, applicationId); + return handle; + } + + public Set<Long> getPendingTokensForUser(int userId) { + if (!tokenMap.containsKey(userId)) { + return Collections.emptySet(); + } + return tokenMap.get(userId).keySet(); + } + + public boolean removePendingToken(long handle, int userId) { + if (!tokenMap.containsKey(userId)) { + return false; + } + return tokenMap.get(userId).remove(handle) != null; + } + + public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken, + int userId) { + if (!tokenMap.containsKey(userId)) { + return false; + } + byte[] applicationId = tokenMap.get(userId).get(handle); + if (applicationId == null) { + return false; + } + if (!loadEscrowData(authToken, userId)) { + Log.w(TAG, "User is not escrowable"); + return false; + } + createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken, + applicationId, 0L, userId); + tokenMap.get(userId).remove(handle); + return true; + } + + private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken, + byte[] applicationId, long sid, int userId) { + final byte[] secret; + if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { + secret = authToken.computeP0(); + } else { + secret = authToken.syntheticPassword.getBytes(); + } + byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid); + byte[] blob = new byte[content.length + 1 + 1]; + blob[0] = SYNTHETIC_PASSWORD_VERSION; + blob[1] = type; + System.arraycopy(content, 0, blob, 2, content.length); + saveState(SP_BLOB_NAME, blob, handle, userId); + } + + /** + * Decrypt a synthetic password by supplying the user credential and corresponding password + * blob handle generated previously. If the decryption is successful, initiate a GateKeeper + * verification to referesh the SID & Auth token maintained by the system. + */ + public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper, + long handle, String credential, int userId) throws RemoteException { + if (credential == null) { + credential = DEFAULT_PASSWORD; + } + AuthenticationResult result = new AuthenticationResult(); + PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId)); + byte[] pwdToken = computePasswordToken(credential, pwd); + byte[] gkPwdToken = passwordTokenToGkInput(pwdToken); + + GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(userId), 0L, + pwd.passwordHandle, gkPwdToken); + int responseCode = response.getResponseCode(); + if (responseCode == GateKeeperResponse.RESPONSE_OK) { + result.gkResponse = VerifyCredentialResponse.OK; + if (response.getShouldReEnroll()) { + GateKeeperResponse reenrollResponse = gatekeeper.enroll(fakeUid(userId), + pwd.passwordHandle, gkPwdToken, gkPwdToken); + if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) { + pwd.passwordHandle = reenrollResponse.getPayload(); + saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId); + } else { + Log.w(TAG, "Fail to re-enroll user password for user " + userId); + // continue the flow anyway + } + } + } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { + result.gkResponse = new VerifyCredentialResponse(response.getTimeout()); + return result; + } else { + result.gkResponse = VerifyCredentialResponse.ERROR; + return result; + } + + + byte[] applicationId = transformUnderSecdiscardable(pwdToken, + loadSecdiscardable(handle, userId)); + result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, + applicationId, userId); + + // Perform verifyChallenge to refresh auth tokens for GK if user password exists. + result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); + return result; + } + + /** + * Decrypt a synthetic password by supplying an escrow token and corresponding token + * blob handle generated previously. If the decryption is successful, initiate a GateKeeper + * verification to referesh the SID & Auth token maintained by the system. + */ + public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword( + IGateKeeperService gatekeeper, long handle, byte[] token, int userId) + throws RemoteException { + AuthenticationResult result = new AuthenticationResult(); + byte[] applicationId = transformUnderSecdiscardable(token, + loadSecdiscardable(handle, userId)); + result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, + applicationId, userId); + if (result.authToken != null) { + result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); + if (result.gkResponse == null) { + // The user currently has no password. return OK with null payload so null + // is propagated to unlockUser() + result.gkResponse = VerifyCredentialResponse.OK; + } + } else { + result.gkResponse = VerifyCredentialResponse.ERROR; + } + return result; + } + + private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type, + byte[] applicationId, int userId) { + byte[] blob = loadState(SP_BLOB_NAME, handle, userId); + if (blob == null) { + return null; + } + if (blob[0] != SYNTHETIC_PASSWORD_VERSION) { + throw new RuntimeException("Unknown blob version"); + } + if (blob[1] != type) { + throw new RuntimeException("Invalid blob type"); + } + byte[] secret = decryptSPBlob(getHandleName(handle), + Arrays.copyOfRange(blob, 2, blob.length), applicationId); + if (secret == null) { + Log.e(TAG, "Fail to decrypt SP for user " + userId); + return null; + } + AuthenticationToken result = new AuthenticationToken(); + if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { + if (!loadEscrowData(result, userId)) { + Log.e(TAG, "User is not escrowable: " + userId); + return null; + } + result.recreate(secret); + } else { + result.syntheticPassword = new String(secret); + } + return result; + } + + /** + * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle + * if required. + * + * Normally performing verifyChallenge with an AuthenticationToken should always return + * RESPONSE_OK, since user authentication failures are detected earlier when trying to + * decrypt SP. + */ + public VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper, + @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException { + byte[] spHandle = loadSyntheticPasswordHandle(userId); + if (spHandle == null) { + // There is no password handle associated with the given user, i.e. the user is not + // secured by lockscreen and has no SID, so just return here; + return null; + } + VerifyCredentialResponse result; + GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge, + spHandle, auth.deriveGkPassword()); + int responseCode = response.getResponseCode(); + if (responseCode == GateKeeperResponse.RESPONSE_OK) { + result = new VerifyCredentialResponse(response.getPayload()); + if (response.getShouldReEnroll()) { + response = gatekeeper.enroll(userId, spHandle, + spHandle, auth.deriveGkPassword()); + if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) { + spHandle = response.getPayload(); + saveSyntheticPasswordHandle(spHandle, userId); + // Call self again to re-verify with updated handle + return verifyChallenge(gatekeeper, auth, challenge, userId); + } else { + Log.w(TAG, "Fail to re-enroll SP handle for user " + userId); + // Fall through, return existing handle + } + } + } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { + result = new VerifyCredentialResponse(response.getTimeout()); + } else { + result = VerifyCredentialResponse.ERROR; + } + return result; + } + + public boolean existsHandle(long handle, int userId) { + return hasState(SP_BLOB_NAME, handle, userId); + } + + public void destroyTokenBasedSyntheticPassword(long handle, int userId) { + destroySyntheticPassword(handle, userId); + destroyState(SECDISCARDABLE_NAME, true, handle, userId); + } + + public void destroyPasswordBasedSyntheticPassword(long handle, int userId) { + destroySyntheticPassword(handle, userId); + destroyState(SECDISCARDABLE_NAME, true, handle, userId); + destroyState(PASSWORD_DATA_NAME, true, handle, userId); + } + + private void destroySyntheticPassword(long handle, int userId) { + destroyState(SP_BLOB_NAME, true, handle, userId); + destroyState(SP_E0_NAME, true, handle, userId); + destroyState(SP_P1_NAME, true, handle, userId); + destroySPBlobKey(getHandleName(handle)); + } + + private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) { + byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash( + PERSONALISATION_SECDISCARDABLE, rawSecdiscardable); + byte[] result = new byte[data.length + secdiscardable.length]; + System.arraycopy(data, 0, result, 0, data.length); + System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length); + return result; + } + + private byte[] createSecdiscardable(long handle, int userId) { + byte[] data = secureRandom(SECDISCARDABLE_LENGTH); + saveState(SECDISCARDABLE_NAME, data, handle, userId); + return data; + } + + private byte[] loadSecdiscardable(long handle, int userId) { + return loadState(SECDISCARDABLE_NAME, handle, userId); + } + + private boolean hasState(String stateName, long handle, int userId) { + return !ArrayUtils.isEmpty(loadState(stateName, handle, userId)); + } + + private byte[] loadState(String stateName, long handle, int userId) { + return mStorage.readSyntheticPasswordState(userId, handle, stateName); + } + + private void saveState(String stateName, byte[] data, long handle, int userId) { + mStorage.writeSyntheticPasswordState(userId, handle, stateName, data); + } + + private void destroyState(String stateName, boolean secure, long handle, int userId) { + mStorage.deleteSyntheticPasswordState(userId, handle, stateName, secure); + } + + protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) { + return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId); + } + + protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) { + return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid); + } + + protected void destroySPBlobKey(String keyAlias) { + SyntheticPasswordCrypto.destroyBlobKey(keyAlias); + } + + public static long generateHandle() { + SecureRandom rng = new SecureRandom(); + long result; + do { + result = rng.nextLong(); + } while (result == DEFAULT_HANDLE); + return result; + } + + private int fakeUid(int uid) { + return 100000 + uid; + } + + protected static byte[] secureRandom(int length) { + try { + return SecureRandom.getInstance("SHA1PRNG").generateSeed(length); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + + private String getHandleName(long handle) { + return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle); + } + + private byte[] computePasswordToken(String password, PasswordData data) { + return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP, + PASSWORD_TOKEN_LENGTH); + } + + private byte[] passwordTokenToGkInput(byte[] token) { + return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token); + } + + protected long sidFromPasswordHandle(byte[] handle) { + return nativeSidFromPasswordHandle(handle); + } + + protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) { + return nativeScrypt(password.getBytes(), salt, N, r, p, outLen); + } + + native long nativeSidFromPasswordHandle(byte[] handle); + native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen); + + final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + public static String bytesToHex(byte[] bytes) { + if (bytes == null) { + return "null"; + } + char[] hexChars = new char[bytes.length * 2]; + for ( int j = 0; j < bytes.length; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } +} diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index fbc444026539..5c8024ac3170 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4087,9 +4087,10 @@ public class AccountManagerService } @Override - public void addSharedAccountsFromParentUser(int parentUserId, int userId) { + public void addSharedAccountsFromParentUser(int parentUserId, int userId, + String opPackageName) { checkManageOrCreateUsersPermission("addSharedAccountsFromParentUser"); - Account[] accounts = getAccountsAsUser(null, parentUserId, mContext.getOpPackageName()); + Account[] accounts = getAccountsAsUser(null, parentUserId, opPackageName); for (Account account : accounts) { addSharedAccountAsUser(account, userId); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a73eb18b3c61..c2c24b37a81a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -556,7 +556,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Intent sent when remote bugreport collection has been completed private static final String INTENT_REMOTE_BUGREPORT_FINISHED = - "android.intent.action.REMOTE_BUGREPORT_FINISHED"; + "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED"; // Used to indicate that an app transition should be animated. static final boolean ANIMATE = true; diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 7c2460421e35..f75ce250c8e2 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -2507,7 +2507,7 @@ final class ActivityStack extends ConfigurationContainer implements StackWindowL if (!next.hasBeenLaunched) { next.hasBeenLaunched = true; } else if (SHOW_APP_STARTING_PREVIEW && lastStack != null && - mStackSupervisor.isFrontStack(lastStack)) { + mStackSupervisor.isFrontStackOnDisplay(lastStack)) { next.showStartingWindow(null /* prev */, false /* newTask */, false /* taskSwitch */); } @@ -4354,7 +4354,7 @@ final class ActivityStack extends ConfigurationContainer implements StackWindowL // If we have a watcher, preflight the move before committing to it. First check // for *other* available tasks, but if none are available, then try again allowing the // current task to be selected. - if (mStackSupervisor.isFrontStack(this) && mService.mController != null) { + if (mStackSupervisor.isFrontStackOnDisplay(this) && mService.mController != null) { ActivityRecord next = topRunningActivityLocked(null, taskId); if (next == null) { next = topRunningActivityLocked(null, 0); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 95734a4ed782..4a29872bdb72 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -624,11 +624,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return stack == mFocusedStack; } - /** The top most stack. */ - boolean isFrontStack(ActivityStack stack) { - return isFrontOfStackList(stack, mHomeStack.mStacks); - } - /** The top most stack on its display. */ boolean isFrontStackOnDisplay(ActivityStack stack) { return isFrontOfStackList(stack, stack.mActivityContainer.mActivityDisplay.mStacks); @@ -1103,13 +1098,21 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // Look in other non-focused and non-home stacks. - final ArrayList<ActivityStack> stacks = mHomeStack.mStacks; - for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = stacks.get(stackNdx); - if (stack != focusedStack && isFrontStack(stack) && stack.isFocusable()) { - r = stack.topRunningActivityLocked(); - if (r != null) { - return r; + mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); + + for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { + final int displayId = mTmpOrderedDisplayIds.get(i); + final List<ActivityStack> stacks = mActivityDisplays.get(displayId).mStacks; + if (stacks == null) { + continue; + } + for (int j = stacks.size() - 1; j >= 0; --j) { + final ActivityStack stack = stacks.get(j); + if (stack != focusedStack && isFrontStackOnDisplay(stack) && stack.isFocusable()) { + r = stack.topRunningActivityLocked(); + if (r != null) { + return r; + } } } } @@ -2676,7 +2679,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // In some cases the focused stack isn't the front stack. E.g. pinned stack. // Whenever we are moving the top activity from the front stack we want to make sure to move // the stack to the front. - final boolean wasFront = isFrontStack(prevStack) + final boolean wasFront = isFrontStackOnDisplay(prevStack) && (prevStack.topRunningActivityLocked() == r); if (stackId == DOCKED_STACK_ID && !task.isResizeable()) { diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 7cb223dec5f3..13370461521b 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -448,7 +448,10 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mVerb = VERB_STARTING; scheduleOpTimeOut(); service.startJob(mParams); - } catch (RemoteException e) { + } catch (Exception e) { + // We catch 'Exception' because client-app malice or bugs might induce a wide + // range of possible exception-throw outcomes from startJob() and its handling + // of the client's ParcelableBundle extras. Slog.e(TAG, "Error sending onStart message to '" + mRunningJob.getServiceComponent().getShortClassName() + "' ", e); } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 17b005d8c890..8bc72dee531c 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -481,12 +481,6 @@ public class GnssLocationProvider implements LocationProviderInterface { public void onLost(Network network) { releaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN); } - - @Override - public void onUnavailable() { - // timeout, it was not possible to establish the required connection - releaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED); - } }; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -877,8 +871,7 @@ public class GnssLocationProvider implements LocationProviderInterface { NetworkRequest request = requestBuilder.build(); mConnMgr.requestNetwork( request, - mSuplConnectivityCallback, - ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS); + mSuplConnectivityCallback); } private void handleReleaseSuplConnection(int agpsDataConnStatus) { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 98177feef85b..407262b08392 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -93,7 +93,6 @@ public class MediaSessionService extends SystemService implements Monitor { private final SessionManagerImpl mSessionManagerImpl; private final MediaSessionStack mPriorityStack; - private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>(); private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>(); private final ArrayList<SessionsListenerRecord> mSessionsListeners = new ArrayList<SessionsListenerRecord>(); @@ -145,7 +144,8 @@ public class MediaSessionService extends SystemService implements Monitor { public void updateSession(MediaSessionRecord record) { synchronized (mLock) { - if (!mAllSessions.contains(record)) { + UserRecord user = mUserRecords.get(record.getUserId()); + if (user == null || !user.mSessions.contains(record)) { Log.d(TAG, "Unknown session updated. Ignoring."); return; } @@ -171,7 +171,8 @@ public class MediaSessionService extends SystemService implements Monitor { public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) { boolean updateSessions = false; synchronized (mLock) { - if (!mAllSessions.contains(record)) { + UserRecord user = mUserRecords.get(record.getUserId()); + if (user == null || !user.mSessions.contains(record)) { Log.d(TAG, "Unknown session changed playback state. Ignoring."); return; } @@ -184,7 +185,8 @@ public class MediaSessionService extends SystemService implements Monitor { public void onSessionPlaybackTypeChanged(MediaSessionRecord record) { synchronized (mLock) { - if (!mAllSessions.contains(record)) { + UserRecord user = mUserRecords.get(record.getUserId()); + if (user == null || !user.mSessions.contains(record)) { Log.d(TAG, "Unknown session changed playback type. Ignoring."); return; } @@ -318,7 +320,6 @@ public class MediaSessionService extends SystemService implements Monitor { } mPriorityStack.removeSession(session); - mAllSessions.remove(session); try { session.getCallback().asBinder().unlinkToDeath(session, 0); @@ -455,7 +456,6 @@ public class MediaSessionService extends SystemService implements Monitor { throw new RuntimeException("Media Session owner died prematurely.", e); } - mAllSessions.add(session); mPriorityStack.addSession(session, mCurrentUserIdList.contains(userId)); user.addSessionLocked(session); @@ -1087,16 +1087,10 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { pw.println(mSessionsListeners.size() + " sessions listeners."); - int count = mAllSessions.size(); - pw.println(count + " Sessions:"); - for (int i = 0; i < count; i++) { - mAllSessions.get(i).dump(pw, ""); - pw.println(); - } mPriorityStack.dump(pw, ""); pw.println("User Records:"); - count = mUserRecords.size(); + int count = mUserRecords.size(); for (int i = 0; i < count; i++) { UserRecord user = mUserRecords.get(mUserRecords.keyAt(i)); user.dumpLocked(pw, ""); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 68d993d41c97..f2b55640f20f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -121,6 +121,7 @@ import android.service.notification.NotificationServiceProto; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeProto; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -140,6 +141,7 @@ import android.widget.Toast; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.FastXmlSerializer; @@ -2812,8 +2814,26 @@ public class NotificationManagerService extends SystemService { proto.write(NotificationRecordProto.STATE, NotificationServiceProto.ENQUEUED); } } + List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed(); + N = snoozed.size(); + if (N > 0) { + for (int i = 0; i < N; i++) { + final NotificationRecord nr = snoozed.get(i); + if (filter.filtered && !filter.matches(nr.sbn)) continue; + nr.dump(proto, filter.redact); + proto.write(NotificationRecordProto.STATE, NotificationServiceProto.SNOOZED); + } + } proto.end(records); } + + long zenLog = proto.start(NotificationServiceDumpProto.ZEN); + mZenModeHelper.dump(proto); + for (ComponentName suppressor : mEffectsSuppressors) { + proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString()); + } + proto.end(zenLog); + proto.flush(); } @@ -2899,21 +2919,9 @@ public class NotificationManagerService extends SystemService { } pw.println(" "); } - } - } - - if (!zenOnly) { - pw.println("\n Usage Stats:"); - mUsageStats.dump(pw, " ", filter); - } - - if (!filter.filtered || zenOnly) { - pw.println("\n Zen Mode:"); - pw.print(" mInterruptionFilter="); pw.println(mInterruptionFilter); - mZenModeHelper.dump(pw, " "); - pw.println("\n Zen Log:"); - ZenLog.dump(pw, " "); + mSnoozeHelper.dump(pw, filter); + } } if (!zenOnly) { @@ -2945,8 +2953,13 @@ public class NotificationManagerService extends SystemService { mNotificationAssistants.dump(pw, filter); } - if (!zenOnly) { - mSnoozeHelper.dump(pw, filter); + if (!filter.filtered || zenOnly) { + pw.println("\n Zen Mode:"); + pw.print(" mInterruptionFilter="); pw.println(mInterruptionFilter); + mZenModeHelper.dump(pw, " "); + + pw.println("\n Zen Log:"); + ZenLog.dump(pw, " "); } pw.println("\n Policy access:"); @@ -2964,6 +2977,11 @@ public class NotificationManagerService extends SystemService { r.dump(pw, " ", getContext(), filter.redact); } } + + if (!zenOnly) { + pw.println("\n Usage Stats:"); + mUsageStats.dump(pw, " ", filter); + } } } @@ -3131,7 +3149,9 @@ public class NotificationManagerService extends SystemService { // snoozed apps if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) { - // TODO: log to event log + MetricsLogger.action(r.getLogMaker() + .setType(MetricsProto.MetricsEvent.TYPE_UPDATE) + .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)); if (DBG) { Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey()); } @@ -4159,7 +4179,7 @@ public class NotificationManagerService extends SystemService { if (until < System.currentTimeMillis() && snoozeCriterionId == null) { return; } - // TODO: write to event log + if (DBG) { Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, until, snoozeCriterionId, listenerName)); @@ -4171,6 +4191,11 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { final NotificationRecord r = findNotificationByKeyLocked(key); if (r != null) { + MetricsLogger.action(r.getLogMaker() + .setCategory(MetricsEvent.NOTIFICATION_SNOOZED) + .setType(MetricsEvent.TYPE_CLOSE) + .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA, + snoozeCriterionId == null ? false : true)); cancelNotificationLocked(r, false, REASON_SNOOZED); updateLightsLocked(); if (snoozeCriterionId != null) { @@ -4189,7 +4214,6 @@ public class NotificationManagerService extends SystemService { void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) { String listenerName = listener == null ? null : listener.component.toShortString(); - // TODO: write to event log if (DBG) { Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); } diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index f2aff1102b0b..0cd8cea881ab 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -16,11 +16,14 @@ package com.android.server.notification; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.annotation.NonNull; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -99,7 +102,7 @@ public class SnoozeHelper { return Collections.EMPTY_LIST; } - protected List<NotificationRecord> getSnoozed() { + protected @NonNull List<NotificationRecord> getSnoozed() { List<NotificationRecord> snoozedForUser = new ArrayList<>(); int[] userIds = mUserProfiles.getCurrentProfileIds(); final int N = userIds.length; @@ -270,6 +273,9 @@ public class SnoozeHelper { final NotificationRecord record = pkgRecords.remove(key); if (record != null) { + MetricsLogger.action(record.getLogMaker() + .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) + .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); mCallback.repost(userId, record); } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 66fb9766749a..75190f3e3e89 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -50,14 +50,18 @@ import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings.Global; +import android.service.notification.Condition; import android.service.notification.ConditionProviderService; +import android.service.notification.NotificationServiceDumpProto; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.EventInfo; import android.service.notification.ZenModeConfig.ScheduleInfo; import android.service.notification.ZenModeConfig.ZenRule; +import android.service.notification.ZenModeProto; import android.util.AndroidRuntimeException; import android.util.Log; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; @@ -488,6 +492,24 @@ public class ZenModeHelper { } } + void dump(ProtoOutputStream proto) { + + proto.write(ZenModeProto.ZEN_MODE, mZenMode); + synchronized (mConfig) { + if (mConfig.manualRule != null) { + proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, mConfig.manualRule.toString()); + } + for (ZenRule rule : mConfig.automaticRules.values()) { + if (rule.enabled && rule.condition.state == Condition.STATE_TRUE + && !rule.snoozing) { + proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, rule.toString()); + } + } + proto.write(ZenModeProto.POLICY, mConfig.toNotificationPolicy().toString()); + proto.write(ZenModeProto.SUPPRESSED_EFFECTS, mSuppressedEffects); + } + } + public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mZenMode="); pw.println(Global.zenModeToString(mZenMode)); diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index 9da94b3007ee..06b6f66203aa 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.app.DownloadManager; import android.app.admin.DevicePolicyManager; +import android.companion.CompanionDeviceManager; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -673,6 +674,16 @@ final class DefaultPermissionGrantPolicy { && doesPackageSupportRuntimePermissions(storageManagerPckg)) { grantRuntimePermissionsLPw(storageManagerPckg, STORAGE_PERMISSIONS, true, userId); } + + // Companion devices + PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackageLPr( + CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME); + if (companionDeviceDiscoveryPackage != null + && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) { + grantRuntimePermissionsLPw(companionDeviceDiscoveryPackage, + LOCATION_PERMISSIONS, true, userId); + } + mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId); } } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index db712aeba0cd..b589057ada3e 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -66,6 +66,9 @@ public class PackageDexOptimizer { public static final int DEX_OPT_PERFORMED = 1; public static final int DEX_OPT_FAILED = -1; + /** Special library name that skips shared libraries check during compilation. */ + public static final String SKIP_SHARED_LIBRARY_CHECK = "&"; + private final Installer mInstaller; private final Object mInstallLock; @@ -274,7 +277,7 @@ public class PackageDexOptimizer { // TODO(calin): maybe add a separate call. mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0, /*oatDir*/ null, dexoptFlags, - compilerFilter, info.volumeUuid, /*sharedLibrariesPath*/ null); + compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK); } return DEX_OPT_PERFORMED; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0d63f72ea37f..f43e468f39ac 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -545,9 +545,6 @@ public class PackageManagerService extends IPackageManager.Stub { public static final int REASON_LAST = REASON_CORE_APP; - /** Special library name that skips shared libraries check during compilation. */ - private static final String SKIP_SHARED_LIBRARY_CHECK = "&"; - /** All dangerous permission names in the same order as the events in MetricsEvent */ private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList( Manifest.permission.READ_CALENDAR, @@ -2401,7 +2398,7 @@ public class PackageManagerService extends IPackageManager.Stub { DEXOPT_PUBLIC, getCompilerFilterForReason(REASON_SHARED_APK), StorageManager.UUID_PRIVATE_INTERNAL, - SKIP_SHARED_LIBRARY_CHECK); + PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK); } } catch (FileNotFoundException e) { Slog.w(TAG, "Library not found: " + libPath); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 3085c9cc9d29..570259ba5615 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -1581,6 +1581,11 @@ class ShortcutPackage extends ShortcutPackageItem { Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " still has an icon"); } + if (si.hasMaskableBitmap() && !si.hasIconFile()) { + failed = true; + Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + + " has maskable bitmap but was not saved to a file."); + } if (si.hasIconFile() && si.hasIconResource()) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index d8857b77d961..057e781e76dd 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1216,7 +1216,8 @@ public class ShortcutService extends IShortcutService.Stub { // he XML we'd lose the icon. We just remove all dangling files after saving the XML. shortcut.setIconResourceId(0); shortcut.setIconResName(null); - shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES); + shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | + ShortcutInfo.FLAG_MASKABLE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES); } public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) { @@ -1351,7 +1352,8 @@ public class ShortcutService extends IShortcutService.Stub { shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES); return; } - case Icon.TYPE_BITMAP: { + case Icon.TYPE_BITMAP: + case Icon.TYPE_BITMAP_MASKABLE: { bitmap = icon.getBitmap(); // Don't recycle in this case. break; } @@ -1382,6 +1384,9 @@ public class ShortcutService extends IShortcutService.Stub { shortcut.setBitmapPath(out.getFile().getAbsolutePath()); shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE); + if (icon.getType() == Icon.TYPE_BITMAP_MASKABLE) { + shortcut.addFlags(ShortcutInfo.FLAG_MASKABLE_BITMAP); + } } finally { IoUtils.closeQuietly(out); } diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java index b704eb1d991c..3c73c88b4f5c 100644 --- a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java +++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java @@ -20,7 +20,7 @@ import android.util.Slog; import java.io.File; import java.io.IOException; -import libcore.tzdata.update2.TimeZoneBundleInstaller; +import libcore.tzdata.update2.TimeZoneDistroInstaller; /** * An install receiver responsible for installing timezone data updates. @@ -34,14 +34,14 @@ public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver { private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/"; private static final String UPDATE_METADATA_DIR_NAME = "metadata/"; private static final String UPDATE_VERSION_FILE_NAME = "version"; - private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip"; + private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_distro.zip"; - private final TimeZoneBundleInstaller installer; + private final TimeZoneDistroInstaller installer; public TzDataInstallReceiver() { super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME, UPDATE_VERSION_FILE_NAME); - installer = new TimeZoneBundleInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR); + installer = new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR); } @Override diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index a872ea4602bb..6a8417dc440b 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -105,6 +105,7 @@ class PinnedStackController { private final DisplayMetrics mTmpMetrics = new DisplayMetrics(); private final Rect mTmpInsets = new Rect(); private final Rect mTmpRect = new Rect(); + private final Point mTmpDisplaySize = new Point(); /** * The callback object passed to listeners for them to notify the controller of state changes. @@ -209,6 +210,9 @@ class PinnedStackController { final int top = (int) (stackBounds.centerY() - height / 2f); stackBounds.set(left, top, left + width, top + height); mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction); + if (mIsMinimized) { + applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds)); + } return stackBounds; } @@ -269,11 +273,7 @@ class PinnedStackController { mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds, snapFraction); if (mIsMinimized) { - final Point displaySize = new Point(mDisplayInfo.logicalWidth, - mDisplayInfo.logicalHeight); - mService.getStableInsetsLocked(displayContent.getDisplayId(), mStableInsets); - mSnapAlgorithm.applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds, - displaySize, mStableInsets); + applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds); } notifyMovementBoundsChanged(false /* fromImeAdjustment */); } @@ -387,6 +387,16 @@ class PinnedStackController { } /** + * Applies the minimized offsets to the given stack bounds. + */ + private void applyMinimizedOffset(Rect stackBounds, Rect movementBounds) { + mTmpDisplaySize.set(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); + mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mStableInsets); + mSnapAlgorithm.applyMinimizedOffset(stackBounds, movementBounds, mTmpDisplaySize, + mStableInsets); + } + + /** * @return the pixels for a given dp value. */ private int dpToPx(float dpValue, DisplayMetrics dm) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 412a89824498..ad8fb8c5f73b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6146,6 +6146,7 @@ public class WindowManagerService extends IWindowManager.Stub * Get an array with display ids ordered by focus priority - last items should be given * focus first. Sparse array just maps position to displayId. */ + // TODO: Maintain display list in focus order in ActivityManager and remove this call. public void getDisplaysInFocusOrder(SparseIntArray displaysInFocusOrder) { synchronized(mWindowMap) { mRoot.getDisplaysInFocusOrder(displaysInFocusOrder); diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index eab5d8a48ba3..2c3cda55c9fe 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -19,6 +19,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \ $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \ + $(LOCAL_REL_DIR)/com_android_server_SyntheticPasswordManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \ $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \ $(LOCAL_REL_DIR)/com_android_server_tv_TvUinputBridge.cpp \ @@ -33,12 +34,14 @@ LOCAL_SRC_FILES += \ LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ + external/scrypt/lib/crypto \ frameworks/base/services \ frameworks/base/libs \ frameworks/base/libs/hwui \ frameworks/base/core/jni \ frameworks/native/services \ system/core/libappfuse/include \ + system/gatekeeper/include \ system/security/keystore/include \ $(call include-path-for, libhardware)/hardware \ $(call include-path-for, libhardware_legacy)/hardware_legacy \ @@ -50,6 +53,7 @@ LOCAL_SHARED_LIBRARIES += \ libappfuse \ libbinder \ libcutils \ + libcrypto \ liblog \ libhardware \ libhardware_legacy \ @@ -83,3 +87,5 @@ LOCAL_SHARED_LIBRARIES += \ android.hardware.tv.input@1.0 \ android.hardware.vibrator@1.0 \ android.hardware.vr@1.0 \ + +LOCAL_STATIC_LIBRARIES += libscrypt_static
\ No newline at end of file diff --git a/services/core/jni/com_android_server_SyntheticPasswordManager.cpp b/services/core/jni/com_android_server_SyntheticPasswordManager.cpp new file mode 100644 index 000000000000..a9f7b9fa4e6c --- /dev/null +++ b/services/core/jni/com_android_server_SyntheticPasswordManager.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 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. + */ + +#define LOG_TAG "SyntheticPasswordManager" + +#include "JNIHelp.h" +#include "jni.h" + +#include <android_runtime/Log.h> +#include <utils/Timers.h> +#include <utils/misc.h> +#include <utils/String8.h> +#include <utils/Log.h> +#include <gatekeeper/password_handle.h> + + +extern "C" { +#include "crypto_scrypt.h" +} + +namespace android { + +static jlong android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle(JNIEnv* env, jobject, jbyteArray handleArray) { + + jbyte* data = (jbyte*)env->GetPrimitiveArrayCritical(handleArray, NULL); + + if (data != NULL) { + const gatekeeper::password_handle_t *handle = + reinterpret_cast<const gatekeeper::password_handle_t *>(data); + jlong sid = handle->user_id; + env->ReleasePrimitiveArrayCritical(handleArray, data, JNI_ABORT); + return sid; + } else { + return 0; + } +} + +static jbyteArray android_server_SyntheticPasswordManager_nativeScrypt(JNIEnv* env, jobject, jbyteArray password, jbyteArray salt, jint N, jint r, jint p, jint outLen) { + if (!password || !salt) { + return NULL; + } + + int passwordLen = env->GetArrayLength(password); + int saltLen = env->GetArrayLength(salt); + jbyteArray ret = env->NewByteArray(outLen); + + jbyte* passwordPtr = (jbyte*)env->GetByteArrayElements(password, NULL); + jbyte* saltPtr = (jbyte*)env->GetByteArrayElements(salt, NULL); + jbyte* retPtr = (jbyte*)env->GetByteArrayElements(ret, NULL); + + int rc = crypto_scrypt((const uint8_t *)passwordPtr, passwordLen, + (const uint8_t *)saltPtr, saltLen, N, r, p, (uint8_t *)retPtr, + outLen); + env->ReleaseByteArrayElements(password, passwordPtr, JNI_ABORT); + env->ReleaseByteArrayElements(salt, saltPtr, JNI_ABORT); + env->ReleaseByteArrayElements(ret, retPtr, 0); + + if (!rc) { + return ret; + } else { + SLOGE("scrypt failed"); + return NULL; + } +} + +static const JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"nativeSidFromPasswordHandle", "([B)J", (void*)android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle}, + {"nativeScrypt", "([B[BIIII)[B", (void*)android_server_SyntheticPasswordManager_nativeScrypt}, +}; + +int register_android_server_SyntheticPasswordManager(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/SyntheticPasswordManager", + sMethods, NELEM(sMethods)); +} + +} /* namespace android */ diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 6f505d51b15c..899640e138e3 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -45,6 +45,7 @@ int register_android_server_tv_TvInputHal(JNIEnv* env); int register_android_server_PersistentDataBlockService(JNIEnv* env); int register_android_server_Watchdog(JNIEnv* env); int register_android_server_HardwarePropertiesManagerService(JNIEnv* env); +int register_android_server_SyntheticPasswordManager(JNIEnv* env); }; using namespace android; @@ -85,6 +86,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_Watchdog(env); register_android_server_HardwarePropertiesManagerService(env); register_android_server_storage_AppFuse(env); + register_android_server_SyntheticPasswordManager(env); return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2b2bb48c91a3..dd44aa0a53cb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -244,6 +244,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle"; + private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token"; + private static final int REQUEST_EXPIRE_PASSWORD = 5571; private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); @@ -506,6 +508,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean mAdminBroadcastPending = false; PersistableBundle mInitBundle = null; + long mPasswordTokenHandle = 0; + public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -2557,6 +2561,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, TAG_INITIALIZATION_BUNDLE); } + if (policy.mPasswordTokenHandle != 0) { + out.startTag(null, TAG_PASSWORD_TOKEN_HANDLE); + out.attribute(null, ATTR_VALUE, + Long.toString(policy.mPasswordTokenHandle)); + out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE); + } + out.endTag(null, "policies"); out.endDocument(); @@ -2763,6 +2774,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols")); m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter")); } + } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) { + policy.mPasswordTokenHandle = Long.parseLong( + parser.getAttributeValue(null, ATTR_VALUE)); } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -4074,12 +4088,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mInjector.binderRestoreCallingIdentity(token); } } - @Override public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException { - if (!mHasFeature) { - return false; - } final int callingUid = mInjector.binderGetCallingUid(); final int userHandle = mInjector.userHandleGetCallingUserId(); @@ -4090,15 +4100,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceNotManagedProfile(userHandle, "clear the active password"); } - int quality; synchronized (this) { // If caller has PO (or DO) it can change the password, so see if that's the case first. ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked( null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, callingUid); final boolean preN; if (admin != null) { - preN = getTargetSdk(admin.info.getPackageName(), - userHandle) <= android.os.Build.VERSION_CODES.M; + final int targetSdk = getTargetSdk(admin.info.getPackageName(), userHandle); + if (targetSdk >= Build.VERSION_CODES.O) { + throw new SecurityException("resetPassword() is deprecated for DPC targeting O" + + " or later"); + } + preN = targetSdk <= android.os.Build.VERSION_CODES.M; } else { // Otherwise, make sure the caller has any active admin with the right policy. admin = getActiveAdminForCallerLocked(null, @@ -4149,7 +4162,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } + } + + return resetPasswordInternal(password, 0, null, flags, callingUid, userHandle); + } + private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token, + int flags, int callingUid, int userHandle) { + int quality; + synchronized (this) { quality = getPasswordQuality(null, userHandle, /* parent */ false); if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) { quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; @@ -4239,11 +4260,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Don't do this with the lock held, because it is going to call // back in to the service. final long ident = mInjector.binderClearCallingIdentity(); + final boolean result; try { - if (!TextUtils.isEmpty(password)) { - mLockPatternUtils.saveLockPassword(password, null, quality, userHandle); + if (token == null) { + if (!TextUtils.isEmpty(password)) { + mLockPatternUtils.saveLockPassword(password, null, quality, userHandle); + } else { + mLockPatternUtils.clearLock(null, userHandle); + } + result = true; } else { - mLockPatternUtils.clearLock(null, userHandle); + result = mLockPatternUtils.setLockCredentialWithToken(password, + TextUtils.isEmpty(password) ? LockPatternUtils.CREDENTIAL_TYPE_NONE + : LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + tokenHandle, token, userHandle); } boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0; if (requireEntry) { @@ -4260,8 +4290,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } finally { mInjector.binderRestoreCallingIdentity(ident); } - - return true; + return result; } private boolean isLockScreenSecureUnchecked(int userId) { @@ -9092,13 +9121,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent() .getPackageName(); - final String[] pkgs = mInjector.getPackageManager().getPackagesForUid(callerUid); - - for (String pkg : pkgs) { - if (deviceOwnerPackageName.equals(pkg)) { - return true; + try { + String[] pkgs = mInjector.getIPackageManager().getPackagesForUid(callerUid); + for (String pkg : pkgs) { + if (deviceOwnerPackageName.equals(pkg)) { + return true; + } + } + } catch (RemoteException e) { + return false; } - } } return false; @@ -10621,4 +10653,98 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceDeviceOwnerOrManageUsers(); return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime; } + + @Override + public boolean setResetPasswordToken(ComponentName admin, byte[] token) { + if (!mHasFeature) { + return false; + } + if (token == null || token.length < 32) { + throw new IllegalArgumentException("token must be at least 32-byte long"); + } + synchronized (this) { + final int userHandle = mInjector.userHandleGetCallingUserId(); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + DevicePolicyData policy = getUserData(userHandle); + long ident = mInjector.binderClearCallingIdentity(); + try { + if (policy.mPasswordTokenHandle != 0) { + mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, userHandle); + } + + policy.mPasswordTokenHandle = mLockPatternUtils.addEscrowToken(token, userHandle); + saveSettingsLocked(userHandle); + return policy.mPasswordTokenHandle != 0; + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + } + + @Override + public boolean clearResetPasswordToken(ComponentName admin) { + if (!mHasFeature) { + return false; + } + synchronized (this) { + final int userHandle = mInjector.userHandleGetCallingUserId(); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + DevicePolicyData policy = getUserData(userHandle); + if (policy.mPasswordTokenHandle != 0) { + long ident = mInjector.binderClearCallingIdentity(); + try { + boolean result = mLockPatternUtils.removeEscrowToken( + policy.mPasswordTokenHandle, userHandle); + policy.mPasswordTokenHandle = 0; + saveSettingsLocked(userHandle); + return result; + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + } + return false; + } + + @Override + public boolean isResetPasswordTokenActive(ComponentName admin) { + synchronized (this) { + final int userHandle = mInjector.userHandleGetCallingUserId(); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + DevicePolicyData policy = getUserData(userHandle); + if (policy.mPasswordTokenHandle != 0) { + long ident = mInjector.binderClearCallingIdentity(); + try { + return mLockPatternUtils.isEscrowTokenActive(policy.mPasswordTokenHandle, + userHandle); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + } + return false; + } + + @Override + public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token, + int flags) { + Preconditions.checkNotNull(token); + synchronized (this) { + final int userHandle = mInjector.userHandleGetCallingUserId(); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + DevicePolicyData policy = getUserData(userHandle); + if (policy.mPasswordTokenHandle != 0) { + final String password = passwordOrNull != null ? passwordOrNull : ""; + return resetPasswordInternal(password, policy.mPasswordTokenHandle, token, + flags, mInjector.binderGetCallingUid(), userHandle); + } else { + Slog.w(LOG_TAG, "No saved token handle"); + } + } + return false; + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 83e209dd5bba..31c8261d0bec 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -35,6 +35,7 @@ import android.os.FileUtils; import android.os.IIncidentManager; import android.os.Looper; import android.os.PowerManager; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; @@ -236,6 +237,8 @@ public final class SystemServer { private static final String START_SENSOR_SERVICE = "StartSensorService"; private Future<?> mSensorServiceStart; + private Future<?> mZygotePreload; + /** * Start the sensor service. This is a blocking call and can take time. @@ -688,6 +691,26 @@ public final class SystemServer { } try { + final String SECONDARY_ZYGOTE_PRELOAD = "SecondaryZygotePreload"; + // We start the preload ~1s before the webview factory preparation, to + // ensure that it completes before the 32 bit relro process is forked + // from the zygote. In the event that it takes too long, the webview + // RELRO process will block, but it will do so without holding any locks. + mZygotePreload = SystemServerInitThreadPool.get().submit(() -> { + try { + Slog.i(TAG, SECONDARY_ZYGOTE_PRELOAD); + BootTimingsTraceLog traceLog = new BootTimingsTraceLog( + SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER); + traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD); + if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) { + Slog.e(TAG, "Unable to preload default resources"); + } + traceLog.traceEnd(); + } catch (Exception ex) { + Slog.e(TAG, "Exception preloading default resources", ex); + } + }, SECONDARY_ZYGOTE_PRELOAD); + traceBeginAndSlog("StartKeyAttestationApplicationIdProviderService"); ServiceManager.addService("sec_key_att_app_id_provider", new KeyAttestationApplicationIdProviderService(context)); @@ -1615,6 +1638,8 @@ public final class SystemServer { BootTimingsTraceLog traceLog = new BootTimingsTraceLog( SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER); traceLog.traceBegin(WEBVIEW_PREPARATION); + ConcurrentUtils.waitForFutureNoInterrupt(mZygotePreload, "Zygote preload"); + mZygotePreload = null; mWebViewUpdateService.prepareWebViewInSystemServer(); traceLog.traceEnd(); }, WEBVIEW_PREPARATION); diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java index 9824c1d12613..9ac8295e816d 100644 --- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java +++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java @@ -19,20 +19,28 @@ package com.android.server.print; import static com.android.internal.util.Preconditions.checkNotNull; -import android.app.PendingIntent; +import android.Manifest; import android.companion.AssociationRequest; +import android.companion.CompanionDeviceManager; +import android.companion.ICompanionDeviceDiscoveryService; +import android.companion.ICompanionDeviceDiscoveryServiceCallback; import android.companion.ICompanionDeviceManager; -import android.companion.ICompanionDeviceManagerService; -import android.companion.IOnAssociateCallback; +import android.companion.IFindDeviceCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.NetworkPolicyManager; import android.os.Binder; import android.os.IBinder; +import android.os.IDeviceIdleController; import android.os.RemoteException; -import android.util.Log; +import android.os.ServiceManager; +import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.server.SystemService; //TODO move to own package! @@ -40,7 +48,8 @@ import com.android.server.SystemService; public class CompanionDeviceManagerService extends SystemService { private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative( - "com.android.companiondevicemanager", ".DeviceDiscoveryService"); + CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, + ".DeviceDiscoveryService"); private static final boolean DEBUG = false; private static final String LOG_TAG = "CompanionDeviceManagerService"; @@ -58,14 +67,13 @@ public class CompanionDeviceManagerService extends SystemService { } class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { - @Override public void associate( AssociationRequest request, - IOnAssociateCallback callback, - String callingPackage) throws RemoteException { + IFindDeviceCallback callback, + String callingPackage) { if (DEBUG) { - Log.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback + Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback + ", callingPackage = " + callingPackage + ")"); } checkNotNull(request); @@ -85,23 +93,24 @@ public class CompanionDeviceManagerService extends SystemService { private ServiceConnection getServiceConnection( final AssociationRequest<?> request, - final IOnAssociateCallback callback, + final IFindDeviceCallback findDeviceCallback, final String callingPackage) { return new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) { - Log.i(LOG_TAG, + Slog.i(LOG_TAG, "onServiceConnected(name = " + name + ", service = " + service + ")"); } try { - ICompanionDeviceManagerService.Stub + ICompanionDeviceDiscoveryService.Stub .asInterface(service) .startDiscovery( request, - getCallback(callingPackage, callback), - callingPackage); + callingPackage, + findDeviceCallback, + getServiceCallback()); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -109,39 +118,50 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Log.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")"); + if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")"); } }; } - private IOnAssociateCallback.Stub getCallback( - String callingPackage, - IOnAssociateCallback propagateTo) { - return new IOnAssociateCallback.Stub() { - - @Override - public void onSuccess(PendingIntent launcher) - throws RemoteException { - if (DEBUG) Log.i(LOG_TAG, "onSuccess(launcher = " + launcher + ")"); - recordSpecialPriviledgesForPackage(callingPackage); - propagateTo.onSuccess(launcher); - } - + private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() { + return new ICompanionDeviceDiscoveryServiceCallback.Stub() { @Override - public void onFailure(CharSequence reason) throws RemoteException { - if (DEBUG) Log.i(LOG_TAG, "onFailure()"); - propagateTo.onFailure(reason); + public void onDeviceSelected(String packageName, int userId) { + grantSpecialAccessPermissionsIfNeeded(packageName, userId); } }; } - void recordSpecialPriviledgesForPackage(String priviledgedPackage) { - //TODO Show dialog before recording notification access -// final SettingStringHelper setting = -// new SettingStringHelper( -// getContext().getContentResolver(), -// Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, -// Binder.getCallingUid()); -// setting.write(ColonDelimitedSet.OfStrings.add(setting.read(), priviledgedPackage)); + private void grantSpecialAccessPermissionsIfNeeded(String packageName, int userId) { + final long identity = Binder.clearCallingIdentity(); + final PackageInfo packageInfo; + try { + packageInfo = getContext().getPackageManager().getPackageInfoAsUser( + packageName, PackageManager.GET_PERMISSIONS, userId); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(LOG_TAG, "Error granting special access permissions to package:" + + packageName, e); + return; + } + try { + if (ArrayUtils.contains(packageInfo.requestedPermissions, + Manifest.permission.RUN_IN_BACKGROUND)) { + IDeviceIdleController idleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + try { + idleController.addPowerSaveWhitelistApp(packageName); + } catch (RemoteException e) { + /* ignore - local call */ + } + } + if (ArrayUtils.contains(packageInfo.requestedPermissions, + Manifest.permission.USE_DATA_IN_BACKGROUND)) { + NetworkPolicyManager.from(getContext()).addUidPolicy( + packageInfo.applicationInfo.uid, + NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); + } + } finally { + Binder.restoreCallingIdentity(identity); + } } } diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java index c89d35c1d84e..c6265bc768f3 100644 --- a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java @@ -134,5 +134,13 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { File storageDir = mStorage.mStorageDir; assertTrue(FileUtils.deleteContents(storageDir)); } + + protected static void assertArrayEquals(byte[] expected, byte[] actual) { + assertTrue(Arrays.equals(expected, actual)); + } + + protected static void assertArrayNotSame(byte[] expected, byte[] actual) { + assertFalse(Arrays.equals(expected, actual)); + } } diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java index 613ec0be0919..cfdb5b1fd4c1 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java @@ -21,9 +21,11 @@ import static org.mockito.Mockito.mock; import android.app.IActivityManager; import android.content.Context; import android.os.Handler; +import android.os.Process; +import android.os.RemoteException; import android.os.storage.IStorageManager; import android.security.KeyStore; -import android.service.gatekeeper.IGateKeeperService; +import android.security.keystore.KeyPermanentlyInvalidatedException; import com.android.internal.widget.LockPatternUtils; @@ -38,16 +40,18 @@ public class LockSettingsServiceTestable extends LockSettingsService { private IActivityManager mActivityManager; private LockPatternUtils mLockPatternUtils; private IStorageManager mStorageManager; + private MockGateKeeperService mGatekeeper; public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore, IActivityManager activityManager, LockPatternUtils lockPatternUtils, - IStorageManager storageManager) { + IStorageManager storageManager, MockGateKeeperService gatekeeper) { super(context); mLockSettingsStorage = storage; mKeyStore = keyStore; mActivityManager = activityManager; mLockPatternUtils = lockPatternUtils; mStorageManager = storageManager; + mGatekeeper = gatekeeper; } @Override @@ -89,13 +93,25 @@ public class LockSettingsServiceTestable extends LockSettingsService { public IStorageManager getStorageManager() { return mStorageManager; } + + @Override + public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) { + return new MockSyntheticPasswordManager(storage, mGatekeeper); + } + + @Override + public int binderGetCallingUid() { + return Process.SYSTEM_UID; + } + + } protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils, - LockSettingsStorage storage, IGateKeeperService gatekeeper, KeyStore keystore, + LockSettingsStorage storage, MockGateKeeperService gatekeeper, KeyStore keystore, IStorageManager storageManager, IActivityManager mActivityManager) { super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils, - storageManager)); + storageManager, gatekeeper)); mGateKeeperService = gatekeeper; } @@ -105,12 +121,18 @@ public class LockSettingsServiceTestable extends LockSettingsService { } @Override - protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException { + protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException, KeyPermanentlyInvalidatedException { byte[] storedData = mStorage.readChildProfileLock(userId); if (storedData == null) { throw new FileNotFoundException("Child profile lock file not found"); } + try { + if (mGateKeeperService.getSecureUserId(userId) == 0) { + throw new KeyPermanentlyInvalidatedException(); + } + } catch (RemoteException e) { + // shouldn't happen. + } return new String(storedData); } - } diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java index 4c2e17172e76..ae9762a81482 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java @@ -123,6 +123,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { UnifiedPassword, PRIMARY_USER_ID); mStorageManager.setIgnoreBadUnlock(false); assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + + //Clear unified challenge + mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword, + PRIMARY_USER_ID); + assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); } public void testManagedProfileSeparateChallenge() throws RemoteException { diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java index e81b02f071a8..18da1a560c74 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java @@ -31,19 +31,36 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { @Override String getLockPatternFilename(int userId) { - return new File(mStorageDir, - super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath(); + return makeDirs(mStorageDir, + super.getLockPatternFilename(userId)).getAbsolutePath(); } @Override String getLockPasswordFilename(int userId) { - return new File(mStorageDir, - super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath(); + return makeDirs(mStorageDir, + super.getLockPasswordFilename(userId)).getAbsolutePath(); } @Override String getChildProfileLockFile(int userId) { - return new File(mStorageDir, - super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath(); + return makeDirs(mStorageDir, + super.getChildProfileLockFile(userId)).getAbsolutePath(); + } + + @Override + protected File getSyntheticPasswordDirectoryForUser(int userId) { + return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser( + userId).getAbsolutePath()); + } + + private File makeDirs(File baseDir, String filePath) { + File path = new File(filePath); + if (path.getParent() == null) { + return new File(baseDir, filePath); + } else { + File mappedDir = new File(baseDir, path.getParent()); + mappedDir.mkdirs(); + return new File(mappedDir, path.getName()); + } } } diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java index d110feaab3c9..c68fbdc0a2ac 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java @@ -329,6 +329,16 @@ public class LockSettingsStorageTests extends AndroidTestCase { assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3)); } + public void testSyntheticPasswordState() { + final byte[] data = {1,2,3,4}; + mStorage.writeSyntheticPasswordState(10, 1234L, "state", data); + assertArrayEquals(data, mStorage.readSyntheticPasswordState(10, 1234L, "state")); + assertEquals(null, mStorage.readSyntheticPasswordState(0, 1234L, "state")); + + mStorage.deleteSyntheticPasswordState(10, 1234L, "state", true); + assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state")); + } + private static void assertArrayEquals(byte[] expected, byte[] actual) { if (!Arrays.equals(expected, actual)) { fail("expected:<" + Arrays.toString(expected) + diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java index 15983cada9c2..bc933418d9fb 100644 --- a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java +++ b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java @@ -149,6 +149,15 @@ public class MockGateKeeperService implements IGateKeeperService { return authTokenMap.get(uid); } + public AuthToken getAuthTokenForSid(long sid) { + for(AuthToken token : authTokenMap.values()) { + if (token.sid == sid) { + return token; + } + } + return null; + } + public void clearAuthToken(int uid) { authTokenMap.remove(uid); } diff --git a/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java new file mode 100644 index 000000000000..93e3fc60a458 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server; + +import android.util.ArrayMap; + +import junit.framework.AssertionFailedError; + +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +public class MockSyntheticPasswordManager extends SyntheticPasswordManager { + + private MockGateKeeperService mGateKeeper; + + public MockSyntheticPasswordManager(LockSettingsStorage storage, + MockGateKeeperService gatekeeper) { + super(storage); + mGateKeeper = gatekeeper; + } + + private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>(); + + @Override + protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) { + if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) { + throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName); + } + ByteBuffer buffer = ByteBuffer.allocate(blob.length); + buffer.put(blob, 0, blob.length); + buffer.flip(); + int len; + len = buffer.getInt(); + byte[] data = new byte[len]; + buffer.get(data); + len = buffer.getInt(); + byte[] appId = new byte[len]; + buffer.get(appId); + long sid = buffer.getLong(); + if (!Arrays.equals(appId, applicationId)) { + throw new AssertionFailedError("Invalid application id"); + } + if (sid != 0 && mGateKeeper.getAuthTokenForSid(sid) == null) { + throw new AssertionFailedError("No valid auth token"); + } + return data; + } + + @Override + protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES + + applicationId.length + Long.BYTES); + buffer.putInt(data.length); + buffer.put(data); + buffer.putInt(applicationId.length); + buffer.put(applicationId); + buffer.putLong(sid); + byte[] result = buffer.array(); + mBlobs.put(blobKeyName, result); + return result; + } + + @Override + protected void destroySPBlobKey(String keyAlias) { + } + + @Override + protected long sidFromPasswordHandle(byte[] handle) { + return new MockGateKeeperService.VerifyHandle(handle).sid; + } + + @Override + protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) { + try { + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10, outLen * 8); + SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + return f.generateSecret(spec).getEncoded(); + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 3a88e9c6303f..c0b79be95da1 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -117,6 +117,7 @@ public class NetworkScoreServiceTest { private static final String SSID = "ssid"; private static final String SSID_2 = "ssid_2"; private static final String SSID_3 = "ssid_3"; + private static final String INVALID_BSSID = "invalid_bssid"; private static final ComponentName RECOMMENDATION_SERVICE_COMP = new ComponentName("newPackageName", "newScoringServiceClass"); private static final ScoredNetwork SCORED_NETWORK = @@ -778,6 +779,54 @@ public class NetworkScoreServiceTest { } @Test + public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_nullSsid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(null); + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_noneSsid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE); + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_emptySsid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(""); + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_invalidBssid() throws Exception { + when(mWifiInfo.getBSSID()).thenReturn(INVALID_BSSID); + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test public void testCurrentNetworkScoreCacheFilter_scoreFiltered() throws Exception { NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); @@ -813,6 +862,24 @@ public class NetworkScoreServiceTest { } @Test + public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception { + List<ScanResult> invalidScanResults = Lists.newArrayList( + new ScanResult(), + createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid), + createScanResult(WifiSsid.NONE, SCORED_NETWORK.networkKey.wifiKey.bssid), + createScanResult(SSID, null), + createScanResult(SSID, INVALID_BSSID) + ); + NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter = + new NetworkScoreService.ScanResultsScoreCacheFilter(() -> invalidScanResults); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test public void testScanResultsScoreCacheFilter_scoresFiltered() throws Exception { NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter = new NetworkScoreService.ScanResultsScoreCacheFilter(() -> mScanResults); diff --git a/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java new file mode 100644 index 000000000000..6e5ade12ce2c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server; + +import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY; +import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY; + +import android.os.RemoteException; +import android.os.UserHandle; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.VerifyCredentialResponse; +import com.android.server.SyntheticPasswordManager.AuthenticationResult; +import com.android.server.SyntheticPasswordManager.AuthenticationToken; + + +/** + * runtest frameworks-services -c com.android.server.SyntheticPasswordTests + */ +public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testPasswordBasedSyntheticPassword() throws RemoteException { + final int USER_ID = 10; + final String PASSWORD = "user-password"; + final String BADPASSWORD = "bad-password"; + MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService); + AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null, + null, USER_ID); + long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService, PASSWORD, + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, USER_ID); + + AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, PASSWORD, USER_ID); + assertEquals(result.authToken.deriveKeyStorePassword(), authToken.deriveKeyStorePassword()); + + result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, BADPASSWORD, USER_ID); + assertNull(result.authToken); + } + + private void disableSyntheticPassword(int userId) throws RemoteException { + mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM); + } + + private void enableSyntheticPassword(int userId) throws RemoteException { + mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM); + } + + private boolean hasSyntheticPassword(int userId) throws RemoteException { + return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0; + } + + public void testPasswordMigration() throws RemoteException { + final String PASSWORD = "testPasswordMigration-password"; + + disableSyntheticPassword(PRIMARY_USER_ID); + mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); + enableSyntheticPassword(PRIMARY_USER_ID); + // Performs migration + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); + + // SP-based verification + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); + } + + private void initializeCredentialUnderSP(String password, int userId) throws RemoteException { + enableSyntheticPassword(userId); + mService.setLockCredential(password, password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD : LockPatternUtils.CREDENTIAL_TYPE_NONE, null, userId); + } + + public void testSyntheticPasswordChangeCredential() throws RemoteException { + final String PASSWORD = "testSyntheticPasswordChangeCredential-password"; + final String NEWPASSWORD = "testSyntheticPasswordChangeCredential-newpassword"; + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID); + mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + } + + public void testSyntheticPasswordVerifyCredential() throws RemoteException { + final String PASSWORD = "testSyntheticPasswordVerifyCredential-password"; + final String BADPASSWORD = "testSyntheticPasswordVerifyCredential-badpassword"; + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + + assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, + mService.verifyCredential(BADPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + } + + public void testSyntheticPasswordClearCredential() throws RemoteException { + final String PASSWORD = "testSyntheticPasswordClearCredential-password"; + final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword"; + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + // clear password + mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID); + assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + + // set a new password + mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + } + + public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException { + final String PASSWORD = "testSyntheticPasswordClearCredential-password"; + final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword"; + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + // clear password + mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + + // set a new password + mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + } + + public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException { + final String PASSWORD = "testSyntheticPasswordClearCredential-password"; + final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword"; + + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + // Untrusted change password + mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + + // Verify the password + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + } + + + public void testManagedProfileUnifiedChallengeMigration() throws RemoteException { + final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd"; + disableSyntheticPassword(PRIMARY_USER_ID); + disableSyntheticPassword(MANAGED_PROFILE_USER_ID); + mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID); + final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); + final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID); + assertTrue(primarySid != 0); + assertTrue(profileSid != 0); + assertTrue(profileSid != primarySid); + + // do migration + enableSyntheticPassword(PRIMARY_USER_ID); + enableSyntheticPassword(MANAGED_PROFILE_USER_ID); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + + // verify + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); + assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID)); + assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); + assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID)); + } + + public void testManagedProfileSeparateChallengeMigration() throws RemoteException { + final String primaryPassword = "testManagedProfileSeparateChallengeMigration-primary"; + final String profilePassword = "testManagedProfileSeparateChallengeMigration-profile"; + mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, MANAGED_PROFILE_USER_ID); + final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID); + final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); + final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID); + assertTrue(primarySid != 0); + assertTrue(profileSid != 0); + assertTrue(profileSid != primarySid); + + // do migration + enableSyntheticPassword(PRIMARY_USER_ID); + enableSyntheticPassword(MANAGED_PROFILE_USER_ID); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode()); + + // verify + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode()); + assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); + assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID)); + assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); + assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID)); + } + + public void testTokenBasedResetPassword() throws RemoteException { + final String PASSWORD = "password"; + final String PATTERN = "123654"; + final String TOKEN = "some-high-entropy-secure-token"; + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); + + long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); + assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID); + + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode()); + assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); + } + + public void testTokenBasedClearPassword() throws RemoteException { + final String PASSWORD = "password"; + final String PATTERN = "123654"; + final String TOKEN = "some-high-entropy-secure-token"; + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); + + long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); + assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); + mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); + + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode()); + assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); + } + + public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException { + final String PASSWORD = "password"; + final String PATTERN = "123654"; + final String NEWPASSWORD = "password"; + final String TOKEN = "some-high-entropy-secure-token"; + initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); + final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); + + long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); + assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID); + + mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); + + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); + assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); + } + + public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException { + final String TOKEN = "some-high-entropy-secure-token"; + enableSyntheticPassword(PRIMARY_USER_ID); + long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); + } + + public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException { + final String TOKEN = "some-high-entropy-secure-token"; + initializeCredentialUnderSP(null, PRIMARY_USER_ID); + long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); + } + + // b/34600579 + //TODO: add non-migration work profile case, and unify/un-unify transition. + //TODO: test token after user resets password + //TODO: test token based reset after unified work challenge + //TODO: test clear password after unified work challenge +} + diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index a186b5956f4a..23a1bb4462f4 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -56,6 +56,7 @@ import android.util.Pair; import com.android.internal.R; import com.android.internal.util.ParcelableString; +import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.UserRestrictionsUtils; @@ -3735,6 +3736,41 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.getPermissionGrantState(admin1, app2, permission)); } + public void testResetPasswordWithToken() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + // test token validation + try { + dpm.setResetPasswordToken(admin1, new byte[31]); + fail("should not have accepted tokens too short"); + } catch (IllegalArgumentException expected) { + } + // test adding a token + final byte[] token = new byte[32]; + final long handle = 123456; + final String password = "password"; + when(mContext.lockPatternUtils.addEscrowToken(eq(token), eq(UserHandle.USER_SYSTEM))) + .thenReturn(handle); + assertTrue(dpm.setResetPasswordToken(admin1, token)); + + // test password activation + when(mContext.lockPatternUtils.isEscrowTokenActive(eq(handle), eq(UserHandle.USER_SYSTEM))) + .thenReturn(true); + assertTrue(dpm.isResetPasswordTokenActive(admin1)); + + // test reset password with token + when(mContext.lockPatternUtils.setLockCredentialWithToken(eq(password), + eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), eq(handle), eq(token), + eq(UserHandle.USER_SYSTEM))) + .thenReturn(true); + assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0)); + + // test removing a token + when(mContext.lockPatternUtils.removeEscrowToken(eq(handle), eq(UserHandle.USER_SYSTEM))) + .thenReturn(true); + assertTrue(dpm.clearResetPasswordToken(admin1)); + } + private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) { when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, userhandle)).thenReturn(isUserSetupComplete ? 1 : 0); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index ed6779c41491..43e2610e187a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -174,6 +174,8 @@ public abstract class DpmTestBase extends AndroidTestCase { anyInt(), eq(UserHandle.getUserId(packageUid))); + doReturn(new String[] {admin.getPackageName()}).when(mMockContext.ipackageManager) + .getPackagesForUid(eq(packageUid)); // Set up getPackageInfo(). markPackageAsInstalled(admin.getPackageName(), ai, UserHandle.getUserId(packageUid)); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 980aa2d34c79..0c5316743e6b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -75,7 +75,9 @@ import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.MaskableIconDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -244,6 +246,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1); final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.icon2)); + final Icon icon3 = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.icon2)); final ShortcutInfo si1 = makeShortcut( "shortcut1", @@ -261,12 +265,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { icon2, makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), /* weight */ 12); - final ShortcutInfo si3 = makeShortcut("shortcut3"); + final ShortcutInfo si3 = makeShortcut( + "shortcut3", + "Title 3", + /* activity */ null, + icon3, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 13); - assertTrue(mManager.setDynamicShortcuts(list(si1, si2))); + assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3))); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut1", "shortcut2"); + "shortcut1", "shortcut2", "shortcut3"); assertEquals(2, mManager.getRemainingCallCount()); // TODO: Check fields @@ -550,7 +560,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_32x32)); - final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource( + final Icon bmp64x64_maskable = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_64x64)); final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_512x512)); @@ -561,7 +571,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { makeShortcutWithIcon("res32x32", res32x32), makeShortcutWithIcon("res64x64", res64x64), makeShortcutWithIcon("bmp32x32", bmp32x32), - makeShortcutWithIcon("bmp64x64", bmp64x64), + makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), makeShortcutWithIcon("bmp512x512", bmp512x512), makeShortcut("none") ))); @@ -691,6 +701,15 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { bmp = pfdToBitmap( mLauncherApps.getShortcutIconFd(CALLING_PACKAGE_1, "bmp32x32", HANDLE_USER_P0)); assertBitmapSize(128, 128, bmp); + + Drawable dr = mLauncherApps.getShortcutIconDrawable( + makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0); + assertTrue(dr instanceof MaskableIconDrawable); + float viewportPercentage = 1 / (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage()); + assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage), + dr.getIntrinsicWidth()); + assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage), + dr.getIntrinsicHeight()); } public void testCleanupDanglingBitmaps() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 562de4148bb1..28ec4fd27ccc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -932,6 +932,74 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { dumpUserFile(USER_10); } + public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException { + mRunningUsers.put(USER_10, true); + + setCaller(CALLING_PACKAGE_1, USER_10); + + final Icon bmp32x32 = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.black_32x32)); + + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext) + .setId("id") + .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class)) + .setIcon(bmp32x32) + .setTitle("title") + .setText("text") + .setDisabledMessage("dismes") + .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) + .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setRank(123) + .setExtras(pb) + .build(); + sorig.setTimestamp(mInjectedCurrentTimeMillis); + + mManager.addDynamicShortcuts(list(sorig)); + + mInjectedCurrentTimeMillis += 1; + final long now = mInjectedCurrentTimeMillis; + mInjectedCurrentTimeMillis += 1; + + dumpsysOnLogcat("before save"); + + // Save and load. + mService.saveDirtyInfo(); + initService(); + mService.handleUnlockUser(USER_10); + + dumpUserFile(USER_10); + dumpsysOnLogcat("after load"); + + ShortcutInfo si; + si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10); + + assertEquals(USER_10, si.getUserId()); + assertEquals(HANDLE_USER_10, si.getUserHandle()); + assertEquals(CALLING_PACKAGE_1, si.getPackage()); + assertEquals("id", si.getId()); + assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("dismes", si.getDisabledMessage()); + assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(0, si.getRank()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE + | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_MASKABLE_BITMAP, + si.getFlags()); + assertNotNull(si.getBitmapPath()); // Something should be set. + assertEquals(0, si.getIconResourceId()); + assertTrue(si.getLastChangedTimestamp() < now); + + dumpUserFile(USER_10); + } + public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException { mRunningUsers.put(USER_10, true); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 96070b88a1a6..7964cf2c8346 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -317,6 +317,15 @@ public class TelecomManager { public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"; /** + * The number of milliseconds that Telecom should wait after disconnecting a call via the + * ACTION_NEW_OUTGOING_CALL broadcast, in order to wait for the app which cancelled the call + * to make a new one. + * @hide + */ + public static final String EXTRA_NEW_OUTGOING_CALL_CANCEL_TIMEOUT = + "android.telecom.extra.NEW_OUTGOING_CALL_CANCEL_TIMEOUT"; + + /** * A boolean meta-data value indicating whether an {@link InCallService} implements an * in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which * would also like to replace the in-call interface should set this meta-data to {@code true} in diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java index d71cc6f7ddf0..6e3a8e879306 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java @@ -50,6 +50,7 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.LruCache; import android.util.TypedValue; +import android.view.DisplayAdjustments; import android.view.ViewGroup.LayoutParams; import java.io.File; @@ -71,7 +72,8 @@ public class Resources_Delegate { DisplayMetrics metrics, Configuration config, LayoutlibCallback layoutlibCallback) { - Resources resources = new Resources(assets, metrics, config); + Resources resources = new Resources(Resources_Delegate.class.getClassLoader()); + resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments())); resources.mContext = context; resources.mLayoutlibCallback = layoutlibCallback; return Resources.mSystem = resources; diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java index bae2dc0c25f5..8ebfc659bbe7 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java @@ -132,7 +132,11 @@ public class LayoutLibTestCallback extends LayoutlibCallback { @Override public Integer getResourceId(ResourceType type, String name) { - return mResources.get(type).get(name); + Map<String, Integer> resName2Id = mResources.get(type); + if (resName2Id == null) { + return null; + } + return resName2Id.get(name); } @Override diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml index 368b46bc92dc..ac975026fc9b 100644 --- a/tools/layoutlib/create/create.iml +++ b/tools/layoutlib/create/create.iml @@ -12,9 +12,9 @@ <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module-library"> - <library name="asm-5.0"> + <library name="asm-5.2"> <CLASSES> - <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.0.jar!/" /> + <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.2.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java index 803a7f195a59..18cab7bee12d 100644 --- a/tools/preload2/src/com/android/preload/DeviceUtils.java +++ b/tools/preload2/src/com/android/preload/DeviceUtils.java @@ -16,13 +16,18 @@ package com.android.preload; +import com.android.ddmlib.AdbCommandRejectedException; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; import com.android.preload.classdataretrieval.hprof.Hprof; import com.android.ddmlib.DdmPreferences; import com.android.ddmlib.IDevice; import com.android.ddmlib.IShellOutputReceiver; +import com.android.ddmlib.SyncException; +import com.android.ddmlib.TimeoutException; +import java.io.File; +import java.io.IOException; import java.util.Date; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -32,6 +37,18 @@ import java.util.concurrent.TimeUnit; */ public class DeviceUtils { + // Locations + private static final String PRELOADED_CLASSES_FILE = "/etc/preloaded-classes"; + // Shell commands + private static final String CREATE_EMPTY_PRELOADED_CMD = "touch " + PRELOADED_CLASSES_FILE; + private static final String DELETE_CACHE_CMD = "rm /data/dalvik-cache/*/*boot.art"; + private static final String DELETE_PRELOADED_CMD = "rm " + PRELOADED_CLASSES_FILE; + private static final String READ_PRELOADED_CMD = "cat " + PRELOADED_CLASSES_FILE; + private static final String START_SHELL_CMD = "start"; + private static final String STOP_SHELL_CMD = "stop"; + private static final String REMOUNT_SYSTEM_CMD = "mount -o rw,remount /system"; + private static final String UNSET_BOOTCOMPLETE_CMD = "setprop dev.bootcomplete \"0\""; + public static void init(int debugPort) { DdmPreferences.setSelectedDebugPort(debugPort); @@ -119,43 +136,56 @@ public class DeviceUtils { return !ret.contains("No such file or directory"); } - /** - * Remove files involved in a standard build that interfere with collecting data. This will - * remove /etc/preloaded-classes, which determines which classes are allocated already in the - * boot image. It also deletes any compiled boot image on the device. Then it restarts the - * device. - * - * This is a potentially long-running operation, as the boot after the deletion may take a while. - * The method will abort after the given timeout. - */ - public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) { - String oldContent = - DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS); - if (oldContent.trim().equals("")) { - System.out.println("Preloaded-classes already empty."); - return true; - } - - // Stop the system server etc. - doShell(device, "stop", 100, TimeUnit.MILLISECONDS); - - // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount," - // but AndroidDebugBridge doesn't expose it. - doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS); - doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); - // We do need an empty file. - doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); - - // Delete the files in the dalvik cache. - doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS); - - // We'll try to use dev.bootcomplete to know when the system server is back up. But stop - // doesn't reset it, so do it manually. - doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS); + /** + * Write over the preloaded-classes file with an empty or existing file and regenerate the boot + * image as necessary. + * + * @param device + * @param pcFile + * @param bootTimeout + * @throws AdbCommandRejectedException + * @throws IOException + * @throws TimeoutException + * @throws SyncException + * @return true if successfully overwritten, false otherwise + */ + public static boolean overwritePreloaded(IDevice device, File pcFile, long bootTimeout) + throws AdbCommandRejectedException, IOException, TimeoutException, SyncException { + boolean writeEmpty = (pcFile == null); + if (writeEmpty) { + // Check if the preloaded-classes file is already empty. + String oldContent = + doShellReturnString(device, READ_PRELOADED_CMD, 1, TimeUnit.SECONDS); + if (oldContent.trim().equals("")) { + System.out.println("Preloaded-classes already empty."); + return true; + } + } - // Start the system server. - doShell(device, "start", 100, TimeUnit.MILLISECONDS); + // Stop the system server etc. + doShell(device, STOP_SHELL_CMD, 1, TimeUnit.SECONDS); + // Remount the read-only system partition + doShell(device, REMOUNT_SYSTEM_CMD, 1, TimeUnit.SECONDS); + // Delete the preloaded-classes file + doShell(device, DELETE_PRELOADED_CMD, 1, TimeUnit.SECONDS); + // Delete the dalvik cache files + doShell(device, DELETE_CACHE_CMD, 1, TimeUnit.SECONDS); + if (writeEmpty) { + // Write an empty preloaded-classes file + doShell(device, CREATE_EMPTY_PRELOADED_CMD, 500, TimeUnit.MILLISECONDS); + } else { + // Push the new preloaded-classes file + device.pushFile(pcFile.getAbsolutePath(), PRELOADED_CLASSES_FILE); + } + // Manually reset the boot complete flag + doShell(device, UNSET_BOOTCOMPLETE_CMD, 1, TimeUnit.SECONDS); + // Restart system server on the device + doShell(device, START_SHELL_CMD, 1, TimeUnit.SECONDS); + // Wait for the boot complete flag and return the outcome. + return waitForBootComplete(device, bootTimeout); + } + private static boolean waitForBootComplete(IDevice device, long timeout) { // Do a loop checking each second whether bootcomplete. Wait for at most the given // threshold. Date startDate = new Date(); @@ -178,7 +208,7 @@ public class DeviceUtils { Date endDate = new Date(); long seconds = TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS); - if (seconds > preloadedWaitTimeInSeconds) { + if (seconds > timeout) { return false; } } diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java index c42a19b58811..2265e9557c4b 100644 --- a/tools/preload2/src/com/android/preload/Main.java +++ b/tools/preload2/src/com/android/preload/Main.java @@ -29,6 +29,7 @@ import com.android.preload.actions.RunMonkeyAction; import com.android.preload.actions.ScanAllPackagesAction; import com.android.preload.actions.ScanPackageAction; import com.android.preload.actions.ShowDataAction; +import com.android.preload.actions.WritePreloadedClassesAction; import com.android.preload.classdataretrieval.ClassDataRetriever; import com.android.preload.classdataretrieval.hprof.Hprof; import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever; @@ -96,6 +97,7 @@ public class Main { public final static String COMPUTE_FILE_CMD = "comp"; public final static String EXPORT_CMD = "export"; public final static String IMPORT_CMD = "import"; + public final static String WRITE_CMD = "write"; /** * @param args @@ -132,6 +134,7 @@ public class Main { null)); actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel, CLASS_PRELOAD_BLACKLIST)); + actions.add(new WritePreloadedClassesAction(clientUtils, null, dataTableModel)); actions.add(new ShowDataAction(dataTableModel)); actions.add(new ImportAction(dataTableModel)); actions.add(new ExportAction(dataTableModel)); @@ -200,6 +203,11 @@ public class Main { ui.input(it.next()); ui.confirmYes(); ui.output(new File(it.next())); + // Operation: Write preloaded classes from a specific file + } else if (WRITE_CMD.equals(op)) { + System.out.println("Writing preloaded classes."); + ui.action(WritePreloadedClassesAction.class); + ui.input(new File(it.next())); } } } catch (NoSuchElementException e) { @@ -305,8 +313,16 @@ public class Main { Main.getUI().showMessageDialog("The device will reboot. This will potentially take a " + "long time. Please be patient."); - if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) { - Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!"); + boolean success = false; + try { + success = DeviceUtils.overwritePreloaded(device, null, 15 * 60); + } catch (Exception e) { + System.err.println(e); + } finally { + if (!success) { + Main.getUI().showMessageDialog( + "Removing preloaded-classes failed unexpectedly!"); + } } } } diff --git a/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java new file mode 100644 index 000000000000..9b97f1168df9 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 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.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; +import com.android.preload.DeviceUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.Date; +import java.util.Map; + +public class WritePreloadedClassesAction extends AbstractThreadedDeviceSpecificAction { + private File preloadedClassFile; + + public WritePreloadedClassesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) { + super("Write preloaded classes action", device); + } + + @Override + public void actionPerformed(ActionEvent e) { + File[] files = Main.getUI().showOpenDialog(true); + if (files != null && files.length > 0) { + preloadedClassFile = files[0]; + super.actionPerformed(e); + } + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + try { + // Write the new file with a 5-minute timeout + DeviceUtils.overwritePreloaded(device, preloadedClassFile, 5 * 60); + } catch (Exception e) { + System.err.println(e); + } finally { + Main.getUI().hideWaitDialog(); + } + } +} |