diff options
355 files changed, 12328 insertions, 4215 deletions
diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java index a7a81f2d20bb..767434d0831c 100644 --- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java +++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java @@ -91,7 +91,7 @@ public class TextClassifierPerfTest { private static ConversationActions.Request createConversationActionsRequest(CharSequence text) { ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(text) .build(); return new ConversationActions.Request.Builder(Collections.singletonList(message)) diff --git a/api/current.txt b/api/current.txt index 55f5a6d7fdba..bb6dbeba2e7a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5466,9 +5466,11 @@ package android.app { public static final class Notification.BubbleMetadata implements android.os.Parcelable { method public int describeContents(); + method public boolean getAutoExpandBubble(); method public int getDesiredHeight(); method public android.graphics.drawable.Icon getIcon(); method public android.app.PendingIntent getIntent(); + method public boolean getSuppressInitialNotification(); method public CharSequence getTitle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR; @@ -5477,9 +5479,11 @@ package android.app { public static class Notification.BubbleMetadata.Builder { ctor public Notification.BubbleMetadata.Builder(); method public android.app.Notification.BubbleMetadata build(); + method public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean); method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int); method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon); method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent); + method public android.app.Notification.BubbleMetadata.Builder setSuppressInitialNotification(boolean); method public android.app.Notification.BubbleMetadata.Builder setTitle(CharSequence); } @@ -6579,7 +6583,6 @@ package android.app.admin { } public class DevicePolicyManager { - method public void addCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String); method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int); method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String); method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting); @@ -6609,7 +6612,7 @@ package android.app.admin { method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName); method public boolean getCameraDisabled(@Nullable android.content.ComponentName); method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException; - method @NonNull public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName); + method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName); method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName); method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName); method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName); @@ -6699,7 +6702,6 @@ package android.app.admin { method public int logoutUser(@NonNull android.content.ComponentName); method public void reboot(@NonNull android.content.ComponentName); method public void removeActiveAdmin(@NonNull android.content.ComponentName); - method public boolean removeCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String); method public boolean removeCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String); method public boolean removeKeyPair(@Nullable android.content.ComponentName, @NonNull String); method public boolean removeOverrideApn(@NonNull android.content.ComponentName, int); @@ -6721,6 +6723,7 @@ package android.app.admin { method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean); method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean); method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException; + method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>); method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean); method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean); method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>); @@ -6788,7 +6791,7 @@ package android.app.admin { method public void uninstallCaCert(@Nullable android.content.ComponentName, byte[]); method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting); method public void wipeData(int); - method public void wipeData(int, CharSequence); + method public void wipeData(int, @NonNull CharSequence); field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN"; field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE"; field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED"; @@ -6932,6 +6935,7 @@ package android.app.admin { field public static final int WIPE_EUICC = 4; // 0x4 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 + field public static final int WIPE_SILENTLY = 8; // 0x8 } public abstract static class DevicePolicyManager.InstallUpdateCallback { @@ -10222,6 +10226,7 @@ package android.content { field public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED"; field public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED"; field public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE"; + field public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME"; field public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED"; field public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED"; field public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE"; @@ -11430,6 +11435,7 @@ package android.content.pm { method public int getSessionId(); method public long getSize(); method public int getStagedSessionErrorCode(); + method public String getStagedSessionErrorMessage(); method public boolean isActive(); method public boolean isMultiPackage(); method public boolean isSealed(); @@ -25947,7 +25953,6 @@ package android.media { method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession(); method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2); method public final void removeSession(@NonNull android.media.MediaSession2); - field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; } public static class MediaSession2Service.MediaNotification { @@ -27470,6 +27475,7 @@ package android.media.session { method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens(); method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo); method public void notifySession2Created(@NonNull android.media.Session2Token); + method public void notifySession2Destroyed(@NonNull android.media.Session2Token); method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener); } @@ -29230,6 +29236,7 @@ package android.net { method public android.os.ParcelFileDescriptor establish(); method public android.net.VpnService.Builder setBlocking(boolean); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); + method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(String); method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); @@ -29838,7 +29845,7 @@ package android.net.wifi { method @Deprecated public boolean disableNetwork(int); method @Deprecated public boolean disconnect(); method @Deprecated public boolean enableNetwork(int, boolean); - method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks(); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks(); method public android.net.wifi.WifiInfo getConnectionInfo(); method public android.net.DhcpInfo getDhcpInfo(); method public int getMaxNumberOfNetworkSuggestionsPerApp(); @@ -30329,26 +30336,26 @@ package android.net.wifi.p2p { } public class WifiP2pManager { - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void cancelConnect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void clearLocalServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public android.net.wifi.p2p.WifiP2pManager.Channel initialize(android.content.Context, android.os.Looper, android.net.wifi.p2p.WifiP2pManager.ChannelListener); method public void removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void removeLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener); method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener); method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener); method public void requestP2pState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.P2pStateListener); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener); method public void setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener); method public void setServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ServiceResponseListener); method public void setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener); @@ -35296,11 +35303,16 @@ package android.os { public abstract class VibrationEffect implements android.os.Parcelable { method public static android.os.VibrationEffect createOneShot(long, int); + method public static android.os.VibrationEffect createPrebaked(int); method public static android.os.VibrationEffect createWaveform(long[], int); method public static android.os.VibrationEffect createWaveform(long[], int[], int); method public int describeContents(); field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR; field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff + field public static final int EFFECT_CLICK = 0; // 0x0 + field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1 + field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5 + field public static final int EFFECT_TICK = 2; // 0x2 } public abstract class Vibrator { @@ -38737,6 +38749,7 @@ package android.provider { public static final class Settings.Panel { field public static final String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY"; + field public static final String ACTION_NFC = "android.settings.panel.action.NFC"; field public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME"; } @@ -44028,7 +44041,9 @@ package android.telephony { field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; + field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; @@ -53331,8 +53346,8 @@ package android.view.textclassifier { method @Nullable public CharSequence getText(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; - field public static final android.app.Person PERSON_USER_LOCAL; - field public static final android.app.Person PERSON_USER_REMOTE; + field public static final android.app.Person PERSON_USER_OTHERS; + field public static final android.app.Person PERSON_USER_SELF; } public static final class ConversationActions.Message.Builder { @@ -53483,6 +53498,7 @@ package android.view.textclassifier { public final class TextClassificationManager { method @NonNull public android.view.textclassifier.TextClassifier createTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext); + method @NonNull public android.view.textclassifier.TextClassifier getLocalTextClassifier(); method @NonNull public android.view.textclassifier.TextClassifier getTextClassifier(); method public void setTextClassificationSessionFactory(@Nullable android.view.textclassifier.TextClassificationSessionFactory); method public void setTextClassifier(@Nullable android.view.textclassifier.TextClassifier); @@ -53560,7 +53576,7 @@ package android.view.textclassifier { public final class TextClassifierEvent implements android.os.Parcelable { method public int describeContents(); method @NonNull public int[] getActionIndices(); - method @Nullable public String getEntityType(); + method @NonNull public String[] getEntityTypes(); method public int getEventCategory(); method @Nullable public android.view.textclassifier.TextClassificationContext getEventContext(); method public int getEventIndex(); @@ -53573,6 +53589,7 @@ package android.view.textclassifier { method public int getRelativeWordEndIndex(); method public int getRelativeWordStartIndex(); method @Nullable public String getResultId(); + method public float getScore(); method public void writeToParcel(android.os.Parcel, int); field public static final int CATEGORY_CONVERSATION_ACTIONS = 3; // 0x3 field public static final int CATEGORY_LANGUAGE_DETECTION = 4; // 0x4 @@ -53607,7 +53624,7 @@ package android.view.textclassifier { ctor public TextClassifierEvent.Builder(int, int); method @NonNull public android.view.textclassifier.TextClassifierEvent build(); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setActionIndices(@NonNull int...); - method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityType(@Nullable String); + method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityTypes(@NonNull java.lang.String...); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventContext(@Nullable android.view.textclassifier.TextClassificationContext); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventIndex(int); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventTime(long); @@ -53618,6 +53635,7 @@ package android.view.textclassifier { method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordEndIndex(int); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordStartIndex(int); method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setResultId(@Nullable String); + method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setScore(float); } public final class TextLanguage implements android.os.Parcelable { @@ -62368,20 +62386,20 @@ package java.nio { method public abstract Object array(); method public abstract int arrayOffset(); method public final int capacity(); - method public final java.nio.Buffer clear(); - method public final java.nio.Buffer flip(); + method public java.nio.Buffer clear(); + method public java.nio.Buffer flip(); method public abstract boolean hasArray(); method public final boolean hasRemaining(); method public abstract boolean isDirect(); method public abstract boolean isReadOnly(); method public final int limit(); - method public final java.nio.Buffer limit(int); - method public final java.nio.Buffer mark(); + method public java.nio.Buffer limit(int); + method public java.nio.Buffer mark(); method public final int position(); - method public final java.nio.Buffer position(int); + method public java.nio.Buffer position(int); method public final int remaining(); - method public final java.nio.Buffer reset(); - method public final java.nio.Buffer rewind(); + method public java.nio.Buffer reset(); + method public java.nio.Buffer rewind(); } public class BufferOverflowException extends java.lang.RuntimeException { diff --git a/api/system-current.txt b/api/system-current.txt index 315a941d3cbb..9e3e800f8332 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -883,8 +883,8 @@ package android.app.contentsuggestions { public final class ClassificationsRequest implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getExtras(); - method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections(); + method @Nullable public android.os.Bundle getExtras(); + method @NonNull public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR; } @@ -898,8 +898,8 @@ package android.app.contentsuggestions { public final class ContentClassification implements android.os.Parcelable { ctor public ContentClassification(@NonNull String, @NonNull android.os.Bundle); method public int describeContents(); - method public android.os.Bundle getExtras(); - method public String getId(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR; } @@ -907,8 +907,8 @@ package android.app.contentsuggestions { public final class ContentSelection implements android.os.Parcelable { ctor public ContentSelection(@NonNull String, @NonNull android.os.Bundle); method public int describeContents(); - method public android.os.Bundle getExtras(); - method public String getId(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR; } @@ -930,8 +930,8 @@ package android.app.contentsuggestions { public final class SelectionsRequest implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getExtras(); - method public android.graphics.Point getInterestPoint(); + method @Nullable public android.os.Bundle getExtras(); + method @Nullable public android.graphics.Point getInterestPoint(); method public int getTaskId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR; @@ -1053,6 +1053,7 @@ package android.app.role { method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback); method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String); method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>); + field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT"; } public interface RoleManagerCallback { @@ -1158,14 +1159,18 @@ package android.bluetooth { public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean getSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean); field public static final int ACCESS_ALLOWED = 1; // 0x1 field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 + field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED"; field public static final int METADATA_COMPANION_APP = 4; // 0x4 field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 @@ -1807,9 +1812,16 @@ package android.hardware.display { } public final class ColorDisplayManager { + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getNightDisplayAutoMode(); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities(); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setAppSaturationLevel(@NonNull String, @IntRange(from=0, to=100) int); + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayAutoMode(int); + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomEndTime(@NonNull java.time.LocalTime); + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomStartTime(@NonNull java.time.LocalTime); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setSaturationLevel(@IntRange(from=0, to=100) int); + field public static final int AUTO_MODE_CUSTOM_TIME = 1; // 0x1 + field public static final int AUTO_MODE_DISABLED = 0; // 0x0 + field public static final int AUTO_MODE_TWILIGHT = 2; // 0x2 field public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 2; // 0x2 field public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 4; // 0x4 field public static final int CAPABILITY_NONE = 0; // 0x0 @@ -1841,9 +1853,15 @@ package android.hardware.hdmi { public final class HdmiControlManager { method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener); method @Nullable public android.hardware.hdmi.HdmiClient getClient(int); + method @Nullable public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevicesList(); + method public int getPhysicalAddress(); method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient(); + method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient(); method @Nullable public android.hardware.hdmi.HdmiTvClient getTvClient(); + method public boolean isRemoteDeviceConnected(android.hardware.hdmi.HdmiDeviceInfo); + method public void powerOffRemoteDevice(android.hardware.hdmi.HdmiDeviceInfo); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener); + method public void requestRemoteDeviceToBecomeActiveSource(android.hardware.hdmi.HdmiDeviceInfo); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean); field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE"; field public static final int AVR_VOLUME_MUTED = 101; // 0x65 @@ -1933,6 +1951,9 @@ package android.hardware.hdmi { field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa } + @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult { + } + public static interface HdmiControlManager.HotplugEventListener { method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent); } @@ -2059,6 +2080,16 @@ package android.hardware.hdmi { public abstract static class HdmiRecordSources.RecordSource { } + public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient { + method public int getDeviceType(); + method public void selectPort(int, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener); + method public void selectPort(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener); + } + + public static interface HdmiSwitchClient.OnSelectListener { + method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int); + } + public class HdmiTimerRecordSources { method public static boolean checkTimerRecordSource(int, byte[]); method public static android.hardware.hdmi.HdmiTimerRecordSources.Duration durationOf(int, int); @@ -3383,6 +3414,15 @@ package android.media { method public void stop(); } + public final class Session2Token implements android.os.Parcelable { + ctor public Session2Token(@NonNull android.content.Context, @NonNull String, @Nullable android.os.Bundle); + method public void destroy(); + method @NonNull public android.os.Bundle getExtras(); + method public int getPid(); + method public boolean isDestroyed(); + field public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + } + public static class SubtitleData.Builder { ctor public SubtitleData.Builder(); ctor public SubtitleData.Builder(@NonNull android.media.SubtitleData); @@ -4647,14 +4687,13 @@ package android.net.wifi { method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>); - method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState(); method public boolean isDeviceToDeviceRttSupported(); method public boolean isPortableHotspotSupported(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled(); method public boolean isWifiScannerSupported(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int); @@ -4664,7 +4703,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback); + method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int); field public static final int CHANGE_REASON_ADDED = 0; // 0x0 field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2 field public static final int CHANGE_REASON_REMOVED = 1; // 0x1 @@ -4700,19 +4739,6 @@ package android.net.wifi { method public void onSuccess(); } - public static interface WifiManager.NetworkRequestMatchCallback { - method public void onAbort(); - method public void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>); - method public void onUserSelectionCallbackRegistration(@NonNull android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback); - method public void onUserSelectionConnectFailure(@NonNull android.net.wifi.WifiConfiguration); - method public void onUserSelectionConnectSuccess(@NonNull android.net.wifi.WifiConfiguration); - } - - public static interface WifiManager.NetworkRequestUserSelectionCallback { - method public void reject(); - method public void select(@NonNull android.net.wifi.WifiConfiguration); - } - public static interface WifiManager.WifiUsabilityStatsListener { method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry); } @@ -5461,8 +5487,9 @@ package android.permission { method public final android.os.IBinder onBind(android.content.Intent); method public abstract int onCountPermissionApps(@NonNull java.util.List<java.lang.String>, boolean, boolean); method @NonNull public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String); + method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onGetPermissionUsages(boolean, long); method public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream); - method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onPermissionUsageResult(boolean, long); + method public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String); method public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String); method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String); field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService"; @@ -5626,6 +5653,18 @@ package android.provider { field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant"; } + public static interface DeviceConfig.ActivityManager { + field public static final String KEY_COMPACT_ACTION_1 = "compact_action_1"; + field public static final String KEY_COMPACT_ACTION_2 = "compact_action_2"; + field public static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; + field public static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; + field public static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; + field public static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; + field public static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; + field public static final String KEY_USE_COMPACTION = "use_compaction"; + field public static final String NAMESPACE = "activity_manager"; + } + public static interface DeviceConfig.FsiBoot { field public static final String NAMESPACE = "fsi_boot"; field public static final String OOB_ENABLED = "oob_enabled"; @@ -5642,6 +5681,11 @@ package android.provider { method public void onPropertyChanged(String, String, String); } + public static interface DeviceConfig.Storage { + field public static final String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled"; + field public static final String NAMESPACE = "storage"; + } + public static interface DeviceConfig.Telephony { field public static final String NAMESPACE = "telephony"; field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer"; @@ -7967,10 +8011,10 @@ package android.telephony.euicc { method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus(); field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED"; - field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION"; field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + field public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; // 0x4 field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2 field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1 field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3 @@ -8663,12 +8707,20 @@ package android.telephony.ims { public class ProvisioningManager { method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int); + method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningIntValue(int, int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningStringValue(int, String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); + field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b + field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a + field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0 + field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1 + field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; + field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY"; } public static class ProvisioningManager.Callback { diff --git a/api/test-current.txt b/api/test-current.txt index 1b5bb10e959b..0f2ba12bd9ad 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -11,9 +11,14 @@ package android { field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; + field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; } + public static final class R.array { + field public static final int config_defaultRoleHolders = 17235974; // 0x1070006 + } + } package android.animation { @@ -332,6 +337,7 @@ package android.app.role { method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String); method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback); + field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT"; } public interface RoleManagerCallback { @@ -1299,15 +1305,11 @@ package android.os { method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context); method public abstract long getDuration(); method protected static int scale(int, float, int); - field public static final int EFFECT_CLICK = 0; // 0x0 - field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1 - field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5 field public static final int EFFECT_POP = 4; // 0x4 field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0 field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1 field public static final int EFFECT_STRENGTH_STRONG = 2; // 0x2 field public static final int EFFECT_THUD = 3; // 0x3 - field public static final int EFFECT_TICK = 2; // 0x2 field public static final int[] RINGTONES; } diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 3defdc5467c8..062ba655640e 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -102,7 +102,17 @@ public class Bmgr { String op = nextArg(); Slog.v(TAG, "Running " + op + " for user:" + userId); - if (!isBmgrActive(userId)) { + if (mBmgr == null) { + System.err.println(BMGR_NOT_RUNNING_ERR); + return; + } + + if ("activate".equals(op)) { + doActivateService(userId); + return; + } + + if (!isBackupActive(userId)) { return; } @@ -175,12 +185,7 @@ public class Bmgr { showUsage(); } - boolean isBmgrActive(@UserIdInt int userId) { - if (mBmgr == null) { - System.err.println(BMGR_NOT_RUNNING_ERR); - return false; - } - + boolean isBackupActive(@UserIdInt int userId) { try { if (!mBmgr.isBackupServiceActive(userId)) { System.err.println(BMGR_NOT_RUNNING_ERR); @@ -845,6 +850,27 @@ public class Bmgr { } } + private void doActivateService(int userId) { + String arg = nextArg(); + if (arg == null) { + showUsage(); + return; + } + + try { + boolean activate = Boolean.parseBoolean(arg); + mBmgr.setBackupServiceActive(userId, activate); + System.out.println( + "Backup service now " + + (activate ? "activated" : "deactivated") + + " for user " + + userId); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + private String nextArg() { if (mNextArg >= mArgs.length) { return null; @@ -880,6 +906,7 @@ public class Bmgr { System.err.println(" bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE..."); System.err.println(" bmgr cancel backups"); System.err.println(" bmgr init TRANSPORT..."); + System.err.println(" bmgr activate BOOL"); System.err.println(""); System.err.println("The '--user' option specifies the user on which the operation is run."); System.err.println("It must be the first argument before the operation."); @@ -946,6 +973,11 @@ public class Bmgr { System.err.println(""); System.err.println("The 'init' command initializes the given transports, wiping all data"); System.err.println("from their backing data stores."); + System.err.println(""); + System.err.println("The 'activate' command activates or deactivates the backup service."); + System.err.println("If the argument is 'true' it will be activated, otherwise it will be"); + System.err.println("deactivated. When deactivated, the service will not be running and no"); + System.err.println("operations can be performed until activation."); } private static class BackupMonitor extends IBackupManagerMonitor.Stub { diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp index c455ac0f83af..0c581f3b1a98 100644 --- a/cmds/idmap2/idmap2/Create.cpp +++ b/cmds/idmap2/idmap2/Create.cpp @@ -39,6 +39,7 @@ using android::idmap2::PolicyBitmask; using android::idmap2::PolicyFlags; using android::idmap2::Result; using android::idmap2::utils::kIdmapFilePermissionMask; +using android::idmap2::utils::UidHasWriteAccessToPath; bool Create(const std::vector<std::string>& args, std::ostream& out_error) { std::string target_apk_path; @@ -66,6 +67,13 @@ bool Create(const std::vector<std::string>& args, std::ostream& out_error) { return false; } + const uid_t uid = getuid(); + if (!UidHasWriteAccessToPath(uid, idmap_path)) { + out_error << "error: uid " << uid << " does not have write access to " << idmap_path + << std::endl; + return false; + } + PolicyBitmask fulfilled_policies = 0; if (auto result = PoliciesToBitmask(policies, out_error)) { fulfilled_policies |= *result; diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index a3c752718ee2..f30ce9b08d6e 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -27,6 +27,7 @@ #include "android-base/macros.h" #include "android-base/stringprintf.h" +#include "binder/IPCThreadState.h" #include "utils/String8.h" #include "utils/Trace.h" @@ -38,18 +39,19 @@ #include "idmap2d/Idmap2Service.h" +using android::IPCThreadState; using android::binder::Status; using android::idmap2::BinaryStreamVisitor; using android::idmap2::Idmap; using android::idmap2::IdmapHeader; using android::idmap2::PolicyBitmask; using android::idmap2::Result; +using android::idmap2::utils::kIdmapCacheDir; using android::idmap2::utils::kIdmapFilePermissionMask; +using android::idmap2::utils::UidHasWriteAccessToPath; namespace { -constexpr const char* kIdmapCacheDir = "/data/resource-cache"; - Status ok() { return Status::ok(); } @@ -77,7 +79,13 @@ Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path, Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path, int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) { assert(_aidl_return); + const uid_t uid = IPCThreadState::self()->getCallingUid(); const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + if (!UidHasWriteAccessToPath(uid, idmap_path)) { + *_aidl_return = false; + return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access", + idmap_path.c_str(), uid)); + } if (unlink(idmap_path.c_str()) != 0) { *_aidl_return = false; return error("failed to unlink " + idmap_path + ": " + strerror(errno)); @@ -118,6 +126,13 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + const uid_t uid = IPCThreadState::self()->getCallingUid(); + if (!UidHasWriteAccessToPath(uid, idmap_path)) { + return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss", + idmap_path.c_str(), uid)); + } + const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); if (!target_apk) { return error("failed to load apk " + target_apk_path); @@ -137,7 +152,6 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, } umask(kIdmapFilePermissionMask); - const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); std::ofstream fout(idmap_path); if (fout.fail()) { return error("failed to open idmap path " + idmap_path); diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h index 5c41c49906cd..3f03236d5e1a 100644 --- a/cmds/idmap2/include/idmap2/FileUtils.h +++ b/cmds/idmap2/include/idmap2/FileUtils.h @@ -17,6 +17,8 @@ #ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ #define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ +#include <sys/types.h> + #include <functional> #include <memory> #include <string> @@ -24,6 +26,7 @@ namespace android::idmap2::utils { +constexpr const char* kIdmapCacheDir = "/data/resource-cache"; constexpr const mode_t kIdmapFilePermissionMask = 0133; // u=rw,g=r,o=r typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)> @@ -35,6 +38,8 @@ std::unique_ptr<std::string> ReadFile(int fd); std::unique_ptr<std::string> ReadFile(const std::string& path); +bool UidHasWriteAccessToPath(uid_t uid, const std::string& path); + } // namespace android::idmap2::utils #endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp index 0255727fc8c6..a9b68cd6d5d5 100644 --- a/cmds/idmap2/libidmap2/FileUtils.cpp +++ b/cmds/idmap2/libidmap2/FileUtils.cpp @@ -19,12 +19,20 @@ #include <unistd.h> #include <cerrno> +#include <climits> +#include <cstdlib> +#include <cstring> #include <fstream> #include <memory> #include <string> #include <utility> #include <vector> +#include "android-base/file.h" +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "private/android_filesystem_config.h" + #include "idmap2/FileUtils.h" namespace android::idmap2::utils { @@ -77,4 +85,26 @@ std::unique_ptr<std::string> ReadFile(int fd) { return r == 0 ? std::move(str) : nullptr; } +#ifdef __ANDROID__ +bool UidHasWriteAccessToPath(uid_t uid, const std::string& path) { + // resolve symlinks and relative paths; the directories must exist + std::string canonical_path; + if (!base::Realpath(base::Dirname(path), &canonical_path)) { + return false; + } + + const std::string cache_subdir = base::StringPrintf("%s/", kIdmapCacheDir); + if (canonical_path == kIdmapCacheDir || + canonical_path.compare(0, cache_subdir.size(), cache_subdir) == 0) { + // limit access to /data/resource-cache to root and system + return uid == AID_ROOT || uid == AID_SYSTEM; + } + return true; +} +#else +bool UidHasWriteAccessToPath(uid_t uid ATTRIBUTE_UNUSED, const std::string& path ATTRIBUTE_UNUSED) { + return true; +} +#endif + } // namespace android::idmap2::utils diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp index d9d9a7f829cf..45f84fe341cc 100644 --- a/cmds/idmap2/tests/FileUtilsTests.cpp +++ b/cmds/idmap2/tests/FileUtilsTests.cpp @@ -22,6 +22,8 @@ #include "gtest/gtest.h" #include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "private/android_filesystem_config.h" #include "idmap2/FileUtils.h" @@ -71,4 +73,25 @@ TEST(FileUtilsTests, ReadFile) { close(pipefd[0]); } +#ifdef __ANDROID__ +TEST(FileUtilsTests, UidHasWriteAccessToPath) { + constexpr const char* tmp_path = "/data/local/tmp/test@idmap"; + const std::string cache_path(base::StringPrintf("%s/test@idmap", kIdmapCacheDir)); + const std::string sneaky_cache_path(base::StringPrintf("/data/../%s/test@idmap", kIdmapCacheDir)); + + ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, tmp_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, cache_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, sneaky_cache_path)); + + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, tmp_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, cache_path)); + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, sneaky_cache_path)); + + constexpr const uid_t AID_SOME_APP = AID_SYSTEM + 1; + ASSERT_TRUE(UidHasWriteAccessToPath(AID_SOME_APP, tmp_path)); + ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, cache_path)); + ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, sneaky_cache_path)); +} +#endif + } // namespace android::idmap2::utils diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp index 4334fa60767b..c550eafe5ffe 100644 --- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp +++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp @@ -38,6 +38,7 @@ #include "gtest/gtest.h" #include "androidfw/PosixUtils.h" +#include "private/android_filesystem_config.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" @@ -69,9 +70,23 @@ void AssertIdmap(const Idmap& idmap, const std::string& target_apk_path, ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \ } while (0) +#ifdef __ANDROID__ +#define SKIP_TEST_IF_CANT_EXEC_IDMAP2 \ + do { \ + const uid_t uid = getuid(); \ + if (uid != AID_ROOT && uid != AID_SYSTEM) { \ + GTEST_SKIP(); \ + } \ + } while (0) +#else +#define SKIP_TEST_IF_CANT_EXEC_IDMAP2 +#endif + } // namespace TEST_F(Idmap2BinaryTests, Create) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + // clang-format off auto result = ExecuteBinary({"idmap2", "create", @@ -97,6 +112,8 @@ TEST_F(Idmap2BinaryTests, Create) { } TEST_F(Idmap2BinaryTests, Dump) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + // clang-format off auto result = ExecuteBinary({"idmap2", "create", @@ -144,6 +161,8 @@ TEST_F(Idmap2BinaryTests, Dump) { } TEST_F(Idmap2BinaryTests, Scan) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk"; const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk"; const std::string idmap_static_1_path = @@ -236,6 +255,8 @@ TEST_F(Idmap2BinaryTests, Scan) { } TEST_F(Idmap2BinaryTests, Lookup) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + // clang-format off auto result = ExecuteBinary({"idmap2", "create", @@ -285,6 +306,8 @@ TEST_F(Idmap2BinaryTests, Lookup) { } TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) { + SKIP_TEST_IF_CANT_EXEC_IDMAP2; + const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST"; // missing mandatory options diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 008a008cdee0..d6b4737f5bbd 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -34,6 +34,7 @@ import "frameworks/base/core/proto/android/server/connectivity/data_stall_event. import "frameworks/base/core/proto/android/server/enums.proto"; import "frameworks/base/core/proto/android/server/location/enums.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; +import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; @@ -207,6 +208,7 @@ message Atom { AttentionManagerServiceResultReported attention_manager_service_result_reported = 143; AdbConnectionChanged adb_connection_changed = 144; SpeechDspStatReported speech_dsp_stat_reported = 145; + UsbContaminantReported usb_contaminant_reported = 146; } // Pulled events will start at field 10000. @@ -2490,24 +2492,32 @@ message PhenotypeFlagStateChanged { /* * Logs when a binary push state changes. - * Logged in Play store + * Logged by the installer via public api. */ message BinaryPushStateChanged { - // Binary package name. - optional string binary_name = 1; - // Version code. - optional int64 version = 2; - // State + // Name of the train. + optional string train_name = 1; + // Version code for a "train" of packages that need to be installed atomically + optional int64 train_version_code = 2; + // After installation of this package, device requires a restart. + optional bool requires_staging = 3; + // Rollback should be enabled for this install. + optional bool rollback_enabled = 4; + // Requires low latency monitoring if possible. + optional bool requires_low_latency_monitor = 5; + enum State { - STATE_UNKNOWN = 0; - DOWNLOAD_START = 1; - DOWNLOAD_DONE = 2; - INSTALL_START = 3; - INSTALL_DONE = 4; - REBOOT_START = 5; - REBOOT_DONE = 6; + UNKNOWN = 0; + INSTALL_REQUESTED = 1; + INSTALL_STARTED = 2; + INSTALL_STAGED_NOT_READY = 3; + INSTALL_STAGED_READY = 4; + INSTALL_SUCCESS = 5; + INSTALL_FAILURE = 6; + INSTALL_CANCELLED = 7; + INSTALLER_ROLLBACK_REQUESTED = 8; } - optional State state = 3; + optional State state = 6; } /** Represents USB port overheat event. */ @@ -4537,3 +4547,13 @@ message SpeechDspStatReported { optional int32 total_crash_count = 3; optional int32 total_recover_count = 4; } + +/** + * Logs USB connector contaminant status. + * + * Logged from: USB Service. + */ +message UsbContaminantReported { + optional string id = 1; + optional android.service.usb.ContaminantPresenceStatus status = 2; +} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a3243a5de72a..ee3d27c7dac8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -77,9 +77,7 @@ import android.graphics.ImageDecoder; import android.hardware.display.DisplayManagerGlobal; import android.net.ConnectivityManager; import android.net.IConnectivityManager; -import android.net.Network; import android.net.Proxy; -import android.net.ProxyInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -1033,15 +1031,10 @@ public final class ActivityThread extends ClientTransactionHandler { NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); } - public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) { + public void updateHttpProxy() { final ConnectivityManager cm = ConnectivityManager.from( getApplication() != null ? getApplication() : getSystemContext()); - final Network network = cm.getBoundNetworkForProcess(); - if (network != null) { - Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); - } else { - Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); - } + Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); } public void processInBackground() { @@ -5960,8 +5953,7 @@ public final class ActivityThread extends ClientTransactionHandler { // crash if we can't get it. final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { - final ProxyInfo proxyInfo = service.getProxyForNetwork(null); - Proxy.setHttpProxySystemProperty(proxyInfo); + Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null)); } catch (RemoteException e) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 4d3711ae7ff5..47398674d74c 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -23,6 +23,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import android.app.ActivityManager.StackInfo; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; @@ -119,6 +120,7 @@ public class ActivityView extends ViewGroup { /** Callback that notifies when the container is ready or destroyed. */ public abstract static class StateCallback { + /** * Called when the container is ready for launching activities. Calling * {@link #startActivity(Intent)} prior to this callback will result in an @@ -127,6 +129,7 @@ public class ActivityView extends ViewGroup { * @see #startActivity(Intent) */ public abstract void onActivityViewReady(ActivityView view); + /** * Called when the container can no longer launch activities. Calling * {@link #startActivity(Intent)} after this callback will result in an @@ -135,11 +138,24 @@ public class ActivityView extends ViewGroup { * @see #startActivity(Intent) */ public abstract void onActivityViewDestroyed(ActivityView view); + + /** + * Called when a task is created inside the container. + * This is a filtered version of {@link TaskStackListener} + */ + public void onTaskCreated(int taskId, ComponentName componentName) { } + /** * Called when a task is moved to the front of the stack inside the container. * This is a filtered version of {@link TaskStackListener} */ public void onTaskMovedToFront(ActivityManager.StackInfo stackInfo) { } + + /** + * Called when a task is about to be removed from the stack inside the container. + * This is a filtered version of {@link TaskStackListener} + */ + public void onTaskRemovalStarted(int taskId) { } } /** @@ -508,14 +524,45 @@ public class ActivityView extends ViewGroup { @Override public void onTaskMovedToFront(int taskId) throws RemoteException { - if (mActivityViewCallback != null) { - StackInfo stackInfo = getTopMostStackInfo(); - // if StackInfo was null or unrelated to the "move to front" then there's no use - // notifying the callback - if (stackInfo != null - && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { - mActivityViewCallback.onTaskMovedToFront(stackInfo); - } + if (mActivityViewCallback == null) { + return; + } + + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or unrelated to the "move to front" then there's no use + // notifying the callback + if (stackInfo != null + && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskMovedToFront(stackInfo); + } + } + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { + if (mActivityViewCallback == null) { + return; + } + + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or unrelated to the task creation then there's no use + // notifying the callback + if (stackInfo != null + && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskCreated(taskId, componentName); + } + } + + @Override + public void onTaskRemovalStarted(int taskId) throws RemoteException { + if (mActivityViewCallback == null) { + return; + } + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or task is on a different display then there's no use + // notifying the callback + if (stackInfo != null + && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskRemovalStarted(taskId); } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ab2430c7a490..364d3c9c8e5d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -39,6 +39,7 @@ import android.os.Parcelable; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; @@ -1712,6 +1713,12 @@ public class AppOpsManager { /** @hide */ public static final String KEY_HISTORICAL_OPS = "historical_ops"; + /** System properties for debug logging of noteOp call sites */ + private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled"; + private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages"; + private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops"; + private static final String DEBUG_LOGGING_TAG = "AppOpsManager"; + /** * Retrieve the op switch that controls the given operation. * @hide @@ -4469,6 +4476,7 @@ public class AppOpsManager { */ @UnsupportedAppUsage public int noteOpNoThrow(int op, int uid, String packageName) { + logNoteOpIfNeeded(op, packageName); try { return mService.noteOperation(op, uid, packageName); } catch (RemoteException e) { @@ -4834,4 +4842,45 @@ public class AppOpsManager { return AppOpsManager.MODE_DEFAULT; } + + private static void logNoteOpIfNeeded(int op, String callingPackage) { + // Check if debug logging propety is enabled. + if (!SystemProperties.getBoolean(DEBUG_LOGGING_ENABLE_PROP, false)) { + return; + } + // Check if this package should be logged. + String packages = SystemProperties.get(DEBUG_LOGGING_PACKAGES_PROP, ""); + if (!"".equals(packages) && callingPackage != null) { + boolean found = false; + for (String pkg : packages.split(",")) { + if (callingPackage.equals(pkg)) { + found = true; + break; + } + } + if (!found) { + return; + } + } + String opStr = opToName(op); + // Check if this app op should be logged + String logOps = SystemProperties.get(DEBUG_LOGGING_OPS_PROP, ""); + if (!"".equals(logOps)) { + boolean found = false; + for (String logOp : logOps.split(",")) { + if (opStr.equals(logOp)) { + found = true; + break; + } + } + if (!found) { + return; + } + } + + // Log a stack trace + Exception here = new Exception("HERE!"); + android.util.Log.i(DEBUG_LOGGING_TAG, "Note operation package= " + callingPackage + + " op= " + opStr, here); + } } diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index cc6c999ed255..db6ad3df17b0 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -434,6 +434,11 @@ interface IActivityTaskManager { void registerRemoteAnimationForNextActivityStart(in String packageName, in RemoteAnimationAdapter adapter); + /** + * Registers remote animations for a display. + */ + void registerRemoteAnimationsForDisplay(int displayId, in RemoteAnimationDefinition definition); + /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ void alwaysShowUnsupportedCompileSdkWarning(in ComponentName activity); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index fcb6c14d052c..c64fcf3e7526 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -100,8 +100,7 @@ oneway interface IApplicationThread { void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, in String[] args); void clearDnsCache(); - void setHttpProxy(in String proxy, in String port, in String exclList, - in Uri pacFileUrl); + void updateHttpProxy(); void setCoreSettings(in Bundle coreSettings); void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info); void scheduleTrimMemory(int level); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b8d748dce9e6..7c550d4332fe 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -84,7 +84,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -5257,7 +5256,11 @@ public class Notification implements Parcelable * @hide */ public RemoteViews makeAmbientNotification() { - return createHeadsUpContentView(false /* increasedHeight */); + RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); + if (headsUpContentView != null) { + return headsUpContentView; + } + return createContentView(); } private void hideLine1Text(RemoteViews result) { @@ -8399,6 +8402,30 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * in its expanded state, with the contents of {@link #getIntent()} in a floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be posted + * <b>without</b> the associated notification in the notification shade. Subsequent update + * notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p> + * + * <p>Generally this flag should only be set if the user has performed an action to request + * or create a bubble.</p> + */ + private static final int FLAG_SUPPRESS_INITIAL_NOTIFICATION = 0x00000002; private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) { mPendingIntent = intent; @@ -8412,6 +8439,7 @@ public class Notification implements Parcelable mTitle = in.readCharSequence(); mIcon = Icon.CREATOR.createFromParcel(in); mDesiredHeight = in.readInt(); + mFlags = in.readInt(); } /** @@ -8444,6 +8472,24 @@ public class Notification implements Parcelable return mDesiredHeight; } + /** + * @return whether this bubble should auto expand when it is posted. + * + * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) + */ + public boolean getAutoExpandBubble() { + return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; + } + + /** + * @return whether this bubble should suppress the initial notification when it is posted. + * + * @see BubbleMetadata.Builder#setSuppressInitialNotification(boolean) + */ + public boolean getSuppressInitialNotification() { + return (mFlags & FLAG_SUPPRESS_INITIAL_NOTIFICATION) != 0; + } + public static final Parcelable.Creator<BubbleMetadata> CREATOR = new Parcelable.Creator<BubbleMetadata>() { @@ -8469,6 +8515,11 @@ public class Notification implements Parcelable out.writeCharSequence(mTitle); mIcon.writeToParcel(out, 0); out.writeInt(mDesiredHeight); + out.writeInt(mFlags); + } + + private void setFlags(int flags) { + mFlags = flags; } /** @@ -8480,6 +8531,7 @@ public class Notification implements Parcelable private CharSequence mTitle; private Icon mIcon; private int mDesiredHeight; + private int mFlags; /** * Constructs a new builder object. @@ -8539,6 +8591,39 @@ public class Notification implements Parcelable } /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted in its expanded state, with the contents of {@link #getIntent()} in a + * floating window. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { + setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); + return this; + } + + /** + * If set and the app creating the bubble is in the foreground, the bubble will be + * posted <b>without</b> the associated notification in the notification shade. + * Subsequent update notifications to this bubble will post a notification in the shade. + * + * <p>If the app creating the bubble is not in the foreground this flag has no effect. + * </p> + * + * <p>Generally this flag should only be set if the user has performed an action to + * request or create a bubble.</p> + */ + public BubbleMetadata.Builder setSuppressInitialNotification( + boolean shouldSupressNotif) { + setFlag(FLAG_SUPPRESS_INITIAL_NOTIFICATION, shouldSupressNotif); + return this; + } + + /** * Creates the {@link BubbleMetadata} defined by this builder. * <p>Will throw {@link IllegalStateException} if required fields have not been set * on this builder.</p> @@ -8553,7 +8638,22 @@ public class Notification implements Parcelable if (mIcon == null) { throw new IllegalStateException("Must supply an icon for the bubble"); } - return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight); + BubbleMetadata data = new BubbleMetadata(mPendingIntent, mTitle, mIcon, + mDesiredHeight); + data.setFlags(mFlags); + return data; + } + + /** + * @hide + */ + public BubbleMetadata.Builder setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + return this; } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 55a3acb16b11..2514eee09da0 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3291,7 +3291,7 @@ public class DevicePolicyManager { */ public int getPasswordMaximumLength(int quality) { PackageManager pm = mContext.getPackageManager(); - if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) { + if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) { return 0; } // Kind-of arbitrary. @@ -3980,6 +3980,10 @@ public class DevicePolicyManager { */ public static final int WIPE_EUICC = 0x0004; + /** + * Flag for {@link #wipeData(int)}: won't show reason for wiping to the user. + */ + public static final int WIPE_SILENTLY = 0x0008; /** * Ask that all user data be wiped. If called as a secondary user, the user will be removed and @@ -3991,9 +3995,10 @@ public class DevicePolicyManager { * be able to call this method; if it has not, a security exception will be thrown. * * @param flags Bit mask of additional options: currently supported flags are - * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}. + * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA}, + * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}. * @throws SecurityException if the calling application does not own an active administrator - * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} + * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} */ public void wipeData(int flags) { throwIfParentInstance("wipeData"); @@ -4013,16 +4018,21 @@ public class DevicePolicyManager { * be able to call this method; if it has not, a security exception will be thrown. * * @param flags Bit mask of additional options: currently supported flags are - * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}. + * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and + * {@link #WIPE_EUICC}. * @param reason a string that contains the reason for wiping data, which can be - * presented to the user. If the string is null or empty, user won't be notified. + * presented to the user. * @throws SecurityException if the calling application does not own an active administrator - * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} - * @throws IllegalArgumentException if the input reason string is null or empty. + * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} + * @throws IllegalArgumentException if the input reason string is null or empty, or if + * {@link #WIPE_SILENTLY} is set. */ - public void wipeData(int flags, CharSequence reason) { + public void wipeData(int flags, @NonNull CharSequence reason) { throwIfParentInstance("wipeData"); - wipeDataInternal(flags, reason != null ? reason.toString() : null); + Preconditions.checkNotNull(reason, "reason string is null"); + Preconditions.checkStringNotEmpty(reason, "reason string is empty"); + Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set"); + wipeDataInternal(flags, reason.toString()); } /** @@ -4033,7 +4043,7 @@ public class DevicePolicyManager { * @see #wipeData(int, CharSequence) * @hide */ - private void wipeDataInternal(int flags, String wipeReasonForUser) { + private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) { if (mService != null) { try { mService.wipeDataWithReason(flags, wipeReasonForUser); @@ -10428,76 +10438,53 @@ public class DevicePolicyManager { } /** - * Whitelists a package that is allowed to access cross profile calendar APIs. + * Whitelists a set of packages that are allowed to access cross-profile calendar APIs. * * <p>Called by a profile owner of a managed profile. * - * @param admin which {@link DeviceAdminReceiver} this request is associated with. - * @param packageName name of the package to be whitelisted. - * @throws SecurityException if {@code admin} is not a profile owner. - * - * @see #removeCrossProfileCalendarPackage(ComponentName, String) - * @see #getCrossProfileCalendarPackages(ComponentName) - */ - public void addCrossProfileCalendarPackage(@NonNull ComponentName admin, - @NonNull String packageName) { - throwIfParentInstance("addCrossProfileCalendarPackage"); - if (mService != null) { - try { - mService.addCrossProfileCalendarPackage(admin, packageName); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - } - - /** - * Removes a package that was allowed to access cross profile calendar APIs - * from the whitelist. - * - * <p>Called by a profile owner of a managed profile. + * <p>Calling with a null value for the set disables the restriction so that all packages + * are allowed to access cross-profile calendar APIs. Calling with an empty set disallows + * all packages from accessing cross-profile calendar APIs. If this method isn't called, + * no package will be allowed to access cross-profile calendar APIs by default. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. - * @param packageName name of the package to be removed from the whitelist. - * @return {@code true} if the package is successfully removed from the whitelist, - * {@code false} otherwise. + * @param packageNames set of packages to be whitelisted. * @throws SecurityException if {@code admin} is not a profile owner. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) * @see #getCrossProfileCalendarPackages(ComponentName) */ - public boolean removeCrossProfileCalendarPackage(@NonNull ComponentName admin, - @NonNull String packageName) { - throwIfParentInstance("removeCrossProfileCalendarPackage"); + public void setCrossProfileCalendarPackages(@NonNull ComponentName admin, + @Nullable Set<String> packageNames) { + throwIfParentInstance("setCrossProfileCalendarPackages"); if (mService != null) { try { - return mService.removeCrossProfileCalendarPackage(admin, packageName); + mService.setCrossProfileCalendarPackages(admin, packageNames == null ? null + : new ArrayList<>(packageNames)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - return false; } /** - * Gets a set of package names that are whitelisted to access cross profile calendar APIs. + * Gets a set of package names that are whitelisted to access cross-profile calendar APIs. * * <p>Called by a profile owner of a managed profile. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @return the set of names of packages that were previously whitelisted via - * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an + * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an * empty set if none have been whitelisted. * @throws SecurityException if {@code admin} is not a profile owner. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) - * @see #removeCrossProfileCalendarPackage(ComponentName, String) + * @see #setCrossProfileCalendarPackages(ComponentName, Set) */ - public @NonNull Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) { + public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) { throwIfParentInstance("getCrossProfileCalendarPackages"); if (mService != null) { try { - return new ArraySet<>(mService.getCrossProfileCalendarPackages(admin)); + final List<String> packageNames = mService.getCrossProfileCalendarPackages(admin); + return packageNames == null ? null : new ArraySet<>(packageNames); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -10506,22 +10493,21 @@ public class DevicePolicyManager { } /** - * Returns if a package is whitelisted to access cross profile calendar APIs. + * Returns if a package is whitelisted to access cross-profile calendar APIs. * * <p>To query for a specific user, use * {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for * that user, and get a {@link DevicePolicyManager} from this context. * * @param packageName the name of the package - * @return {@code true} if the package is whitelisted to access cross profile calendar APIs. + * @return {@code true} if the package is whitelisted to access cross-profile calendar APIs. * {@code false} otherwise. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) - * @see #removeCrossProfileCalendarPackage(ComponentName, String) + * @see #setCrossProfileCalendarPackages(ComponentName, Set) * @see #getCrossProfileCalendarPackages(ComponentName) * @hide */ - public @NonNull boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) { + public boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) { throwIfParentInstance("isPackageAllowedToAccessCalendar"); if (mService != null) { try { @@ -10535,27 +10521,27 @@ public class DevicePolicyManager { } /** - * Gets a set of package names that are whitelisted to access cross profile calendar APIs. + * Gets a set of package names that are whitelisted to access cross-profile calendar APIs. * * <p>To query for a specific user, use * {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for * that user, and get a {@link DevicePolicyManager} from this context. * * @return the set of names of packages that were previously whitelisted via - * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an + * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an * empty set if none have been whitelisted. * - * @see #addCrossProfileCalendarPackage(ComponentName, String) - * @see #removeCrossProfileCalendarPackage(ComponentName, String) + * @see #setCrossProfileCalendarPackages(ComponentName, Set) * @see #getCrossProfileCalendarPackages(ComponentName) * @hide */ - public @NonNull Set<String> getCrossProfileCalendarPackages() { + public @Nullable Set<String> getCrossProfileCalendarPackages() { throwIfParentInstance("getCrossProfileCalendarPackages"); if (mService != null) { try { - return new ArraySet<>(mService.getCrossProfileCalendarPackagesForUser( - myUserId())); + final List<String> packageNames = mService.getCrossProfileCalendarPackagesForUser( + myUserId()); + return packageNames == null ? null : new ArraySet<>(packageNames); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 568becfcdd1a..1751a91caf1a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -424,8 +424,7 @@ interface IDevicePolicyManager { void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener); - void addCrossProfileCalendarPackage(in ComponentName admin, String packageName); - boolean removeCrossProfileCalendarPackage(in ComponentName admin, String packageName); + void setCrossProfileCalendarPackages(in ComponentName admin, in List<String> packageNames); List<String> getCrossProfileCalendarPackages(in ComponentName admin); boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle); List<String> getCrossProfileCalendarPackagesForUser(int userHandle); diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java index 3f715186abfb..9bb39e599135 100644 --- a/core/java/android/app/contentsuggestions/ClassificationsRequest.java +++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java @@ -26,6 +26,11 @@ import android.os.Parcelable; import java.util.List; /** + * Request object used when asking {@link ContentSuggestionsManager} to classify content selections. + * + * <p>The request contains a list of {@link ContentSelection} objects to be classified along with + * implementation specific extras. + * * @hide */ @SystemApi @@ -44,14 +49,14 @@ public final class ClassificationsRequest implements Parcelable { /** * Return request selections. */ - public List<ContentSelection> getSelections() { + public @NonNull List<ContentSelection> getSelections() { return mSelections; } /** - * Return the request extras. + * Return the request extras or {@code null} if there are none. */ - public Bundle getExtras() { + public @Nullable Bundle getExtras() { return mExtras; } diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java index f18520f1053c..2a00b406f814 100644 --- a/core/java/android/app/contentsuggestions/ContentClassification.java +++ b/core/java/android/app/contentsuggestions/ContentClassification.java @@ -23,6 +23,9 @@ import android.os.Parcel; import android.os.Parcelable; /** + * Represents the classification of a content suggestion. The result of a + * {@link ClassificationsRequest} to {@link ContentSuggestionsManager}. + * * @hide */ @SystemApi @@ -32,6 +35,12 @@ public final class ContentClassification implements Parcelable { @NonNull private final Bundle mExtras; + /** + * Default constructor. + * + * @param classificationId implementation specific id for the selection / classification. + * @param extras containing the classification data. + */ public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) { mClassificationId = classificationId; mExtras = extras; @@ -40,14 +49,14 @@ public final class ContentClassification implements Parcelable { /** * Return the classification id. */ - public String getId() { + public @NonNull String getId() { return mClassificationId; } /** * Return the classification extras. */ - public Bundle getExtras() { + public @NonNull Bundle getExtras() { return mExtras; } diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java index a8917f782e83..16b4f3f6456d 100644 --- a/core/java/android/app/contentsuggestions/ContentSelection.java +++ b/core/java/android/app/contentsuggestions/ContentSelection.java @@ -23,6 +23,9 @@ import android.os.Parcel; import android.os.Parcelable; /** + * Represents a suggested selection within a set of on screen content. The result of a + * {@link SelectionsRequest} to {@link ContentSuggestionsManager}. + * * @hide */ @SystemApi @@ -32,6 +35,12 @@ public final class ContentSelection implements Parcelable { @NonNull private final Bundle mExtras; + /** + * Default constructor. + * + * @param selectionId implementation specific id for the selection. + * @param extras containing the data that represents the selection. + */ public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) { mSelectionId = selectionId; mExtras = extras; @@ -40,14 +49,14 @@ public final class ContentSelection implements Parcelable { /** * Return the selection id. */ - public String getId() { + public @NonNull String getId() { return mSelectionId; } /** * Return the selection extras. */ - public Bundle getExtras() { + public @NonNull Bundle getExtras() { return mExtras; } diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java index 16f3e6b35aa4..e3c8bc5c7e9e 100644 --- a/core/java/android/app/contentsuggestions/SelectionsRequest.java +++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java @@ -25,6 +25,12 @@ import android.os.Parcel; import android.os.Parcelable; /** + * The request object used to request content selections from {@link ContentSuggestionsManager}. + * + * <p>Selections are requested for a given taskId as specified by + * {@link android.app.ActivityManager} and optionally take an interest point that specifies the + * point on the screen that should be considered as the most important. + * * @hide */ @SystemApi @@ -49,16 +55,17 @@ public final class SelectionsRequest implements Parcelable { } /** - * Return the request point of interest. + * Return the request point of interest or {@code null} if there is no point of interest for + * this request. */ - public Point getInterestPoint() { + public @Nullable Point getInterestPoint() { return mInterestPoint; } /** - * Return the request extras. + * Return the request extras or {@code null} if there aren't any. */ - public Bundle getExtras() { + public @Nullable Bundle getExtras() { return mExtras; } @@ -99,6 +106,11 @@ public final class SelectionsRequest implements Parcelable { private Point mInterestPoint; private Bundle mExtras; + /** + * Default constructor. + * + * @param taskId of the type used by {@link android.app.ActivityManager} + */ public Builder(int taskId) { mTaskId = taskId; } diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index a6abe0b30f79..ddd531339d39 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -172,6 +172,15 @@ public final class RoleManager { public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP"; /** + * The name of the assistant app role. + * + * @hide + */ + @SystemApi + @TestApi + public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT"; + + /** * The action used to request user approval of a role for an application. * * @hide diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 17cf702bbdea..4d8dc35d7148 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -532,6 +532,28 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL"; /** + * Intent to broadcast silence mode changed. + * Alway contains the extra field {@link #EXTRA_DEVICE} + * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED} + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @SystemApi + public static final String ACTION_SILENCE_MODE_CHANGED = + "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; + + /** + * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent, + * contains whether device is in silence mode as boolean. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SILENCE_ENABLED = + "android.bluetooth.device.extra.SILENCE_ENABLED"; + + /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent. * * @hide @@ -1592,6 +1614,70 @@ public final class BluetoothDevice implements Parcelable { } /** + * Set the Bluetooth device silence mode. + * + * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice} + * is an active device (for A2DP or HFP), the active device for that profile + * will be set to null. + * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP + * active device is null, the {@link BluetoothDevice} will be set as the + * active device for that profile. + * If the {@link BluetoothDevice} is disconnected, it exits silence mode. + * If the {@link BluetoothDevice} is set as the active device for A2DP or + * HFP, while silence mode is enabled, then the device will exit silence mode. + * If the {@link BluetoothDevice} is in silence mode, AVRCP position change + * event and HFP AG indicators will be disabled. + * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot + * enter silence mode. + * + * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @param silence true to enter silence mode, false to exit + * @return true on success, false on error. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setSilenceMode(boolean silence) { + final IBluetooth service = sService; + if (service == null) { + return false; + } + try { + if (getSilenceMode() == silence) { + return true; + } + return service.setSilenceMode(this, silence); + } catch (RemoteException e) { + Log.e(TAG, "setSilenceMode fail", e); + return false; + } + } + + /** + * Get the device silence mode status + * + * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @return true on device in silence mode, otherwise false. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean getSilenceMode() { + final IBluetooth service = sService; + if (service == null) { + return false; + } + try { + return service.getSilenceMode(this); + } catch (RemoteException e) { + Log.e(TAG, "getSilenceMode fail", e); + return false; + } + } + + /** * Sets whether the phonebook access is allowed to this device. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b46203c7a682..9e7aaf652a4d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3047,6 +3047,13 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE"; + /** + * Broadcast Action: Request the media scanner to scan a storage volume and add it to the media database. + * The path to the storage volume is contained in the Intent.mData field. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME"; + /** * Broadcast Action: The "Media Button" was pressed. Includes a single * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that @@ -9969,9 +9976,21 @@ public class Intent implements Parcelable, Cloneable { } /** @hide */ + public void writeToProto(ProtoOutputStream proto) { + // Same input parameters that toString() gives to toShortString(). + writeToProtoWithoutFieldId(proto, true, true, true, false); + } + + /** @hide */ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp, boolean extras, boolean clip) { long token = proto.start(fieldId); + writeToProtoWithoutFieldId(proto, secure, comp, extras, clip); + proto.end(token); + } + + private void writeToProtoWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp, + boolean extras, boolean clip) { if (mAction != null) { proto.write(IntentProto.ACTION, mAction); } @@ -10016,7 +10035,6 @@ public class Intent implements Parcelable, Cloneable { if (mSelector != null) { proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip)); } - proto.end(token); } /** diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index c702b16c97f6..276853d3b860 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -76,4 +76,6 @@ interface IShortcutService { // System API used by framework's ShareSheet (ChooserActivity) ParceledListSlice getShareTargets(String packageName, in IntentFilter filter, int userId); + + boolean hasShareTargets(String packageName, String packageToCheck, int userId); }
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 73b1f4e7e536..2dc014c45fad 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1710,6 +1710,7 @@ public class PackageInstaller { /** {@hide} */ public boolean isSessionFailed; private int mStagedSessionErrorCode; + private String mStagedSessionErrorMessage; /** {@hide} */ @UnsupportedAppUsage @@ -1749,6 +1750,7 @@ public class PackageInstaller { isSessionReady = source.readBoolean(); isSessionFailed = source.readBoolean(); mStagedSessionErrorCode = source.readInt(); + mStagedSessionErrorMessage = source.readString(); } /** @@ -2066,9 +2068,19 @@ public class PackageInstaller { return mStagedSessionErrorCode; } + /** + * Text description of the error code returned by {@code getStagedSessionErrorCode}, or + * empty string if no error was encountered. + */ + public String getStagedSessionErrorMessage() { + return mStagedSessionErrorMessage; + } + /** {@hide} */ - public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode) { + public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode, + String errorMessage) { mStagedSessionErrorCode = errorCode; + mStagedSessionErrorMessage = errorMessage; } @Override @@ -2106,6 +2118,7 @@ public class PackageInstaller { dest.writeBoolean(isSessionReady); dest.writeBoolean(isSessionFailed); dest.writeInt(mStagedSessionErrorCode); + dest.writeString(mStagedSessionErrorMessage); } public static final Parcelable.Creator<SessionInfo> diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d5636d5fc91f..eb59cfc0fc4b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -8512,6 +8512,7 @@ public class PackageParser { collectCerts ? PackageParser.PARSE_COLLECT_CERTIFICATES : 0); pi.packageName = apk.packageName; + ai.packageName = apk.packageName; pi.setLongVersionCode(apk.getLongVersionCode()); ai.setVersionCode(apk.getLongVersionCode()); diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index 4f7acd96aa6b..849fd03eacb3 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -635,4 +635,21 @@ public class ShortcutManager { } }; } + + /** + * Used by framework's ShareSheet (ChooserActivity.java) to check if a given package has share + * target definitions in it's resources. + * + * @param packageName Package to check for share targets. + * @return True if the package has any share target definitions, False otherwise. + * @hide + */ + public boolean hasShareTargets(@NonNull String packageName) { + try { + return mService.hasShareTargets(mContext.getPackageName(), packageName, + injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index fa335c8b9924..27f0b0425ec3 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -23,16 +23,22 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.content.ContentResolver; import android.content.Context; +import android.metrics.LogMaker; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.provider.Settings.Secure; import com.android.internal.R; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.LocalTime; /** * Manages the display's color transforms and modes. @@ -81,7 +87,82 @@ public final class ColorDisplayManager { @SystemApi public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 0x4; + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM_TIME, AUTO_MODE_TWILIGHT }) + public @interface AutoMode {} + + /** + * Auto mode value to prevent Night display from being automatically activated. It can still + * be activated manually via {@link #setNightDisplayActivated(boolean)}. + * + * @see #setNightDisplayAutoMode(int) + * + * @hide + */ + @SystemApi + public static final int AUTO_MODE_DISABLED = 0; + /** + * Auto mode value to automatically activate Night display at a specific start and end time. + * + * @see #setNightDisplayAutoMode(int) + * @see #setNightDisplayCustomStartTime(LocalTime) + * @see #setNightDisplayCustomEndTime(LocalTime) + * + * @hide + */ + @SystemApi + public static final int AUTO_MODE_CUSTOM_TIME = 1; + /** + * Auto mode value to automatically activate Night display from sunset to sunrise. + * + * @see #setNightDisplayAutoMode(int) + * + * @hide + */ + @SystemApi + public static final int AUTO_MODE_TWILIGHT = 2; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC}) + public @interface ColorMode {} + + /** + * Color mode with natural colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_NATURAL = 0; + /** + * Color mode with boosted colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_BOOSTED = 1; + /** + * Color mode with saturated colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_SATURATED = 2; + /** + * Color mode with automatic colors. + * + * @hide + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_AUTOMATIC = 3; + private final ColorDisplayManagerInternal mManager; + private MetricsLogger mMetricsLogger; /** * @hide @@ -91,6 +172,176 @@ public final class ColorDisplayManager { } /** + * (De)activates the night display transform. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayActivated(boolean activated) { + return mManager.setNightDisplayActivated(activated); + } + + /** + * Returns whether the night display transform is currently active. + * + * @hide + */ + public boolean isNightDisplayActivated() { + return mManager.isNightDisplayActivated(); + } + + /** + * Sets the color temperature of the night display transform. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayColorTemperature(int temperature) { + return mManager.setNightDisplayColorTemperature(temperature); + } + + /** + * Gets the color temperature of the night display transform. + * + * @hide + */ + public int getNightDisplayColorTemperature() { + return mManager.getNightDisplayColorTemperature(); + } + + /** + * Returns the current auto mode value controlling when Night display will be automatically + * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or + * {@link #AUTO_MODE_TWILIGHT}. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public @AutoMode int getNightDisplayAutoMode() { + return mManager.getNightDisplayAutoMode(); + } + + /** + * Returns the current auto mode value, without validation, or {@code 1} if the auto mode has + * never been set. + * + * @hide + */ + public int getNightDisplayAutoModeRaw() { + return mManager.getNightDisplayAutoModeRaw(); + } + + /** + * Sets the current auto mode value controlling when Night display will be automatically + * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or + * {@link #AUTO_MODE_TWILIGHT}. + * + * @param autoMode the new auto mode to use + * @return {@code true} if new auto mode was set successfully + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayAutoMode(@AutoMode int autoMode) { + if (autoMode != AUTO_MODE_DISABLED + && autoMode != AUTO_MODE_CUSTOM_TIME + && autoMode != AUTO_MODE_TWILIGHT) { + throw new IllegalArgumentException("Invalid autoMode: " + autoMode); + } + if (mManager.getNightDisplayAutoMode() != autoMode) { + getMetricsLogger().write(new LogMaker( + MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(autoMode)); + } + return mManager.setNightDisplayAutoMode(autoMode); + } + + /** + * Returns the local time when Night display will be automatically activated when using + * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. + * + * @hide + */ + public @NonNull LocalTime getNightDisplayCustomStartTime() { + return mManager.getNightDisplayCustomStartTime().getLocalTime(); + } + + /** + * Sets the local time when Night display will be automatically activated when using + * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. + * + * @param startTime the local time to automatically activate Night display + * @return {@code true} if the new custom start time was set successfully + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayCustomStartTime(@NonNull LocalTime startTime) { + if (startTime == null) { + throw new IllegalArgumentException("startTime cannot be null"); + } + getMetricsLogger().write(new LogMaker( + MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(0)); + return mManager.setNightDisplayCustomStartTime(new Time(startTime)); + } + + /** + * Returns the local time when Night display will be automatically deactivated when using + * {@link #AUTO_MODE_CUSTOM_TIME}. + * + * @hide + */ + public @NonNull LocalTime getNightDisplayCustomEndTime() { + return mManager.getNightDisplayCustomEndTime().getLocalTime(); + } + + /** + * Sets the local time when Night display will be automatically deactivated when using + * {@link #AUTO_MODE_CUSTOM_TIME}. + * + * @param endTime the local time to automatically deactivate Night display + * @return {@code true} if the new custom end time was set successfully + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setNightDisplayCustomEndTime(@NonNull LocalTime endTime) { + if (endTime == null) { + throw new IllegalArgumentException("endTime cannot be null"); + } + getMetricsLogger().write(new LogMaker( + MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(1)); + return mManager.setNightDisplayCustomEndTime(new Time(endTime)); + } + + /** + * Sets the current display color mode. + * + * @hide + */ + public void setColorMode(int colorMode) { + mManager.setColorMode(colorMode); + } + + /** + * Gets the current display color mode. + * + * @hide + */ + public int getColorMode() { + return mManager.getColorMode(); + } + + /** * Returns whether the device has a wide color gamut display. * * @hide @@ -138,6 +389,28 @@ public final class ColorDisplayManager { } /** + * Returns the minimum allowed color temperature (in Kelvin) to tint the display when + * activated. + * + * @hide + */ + public static int getMinimumColorTemperature(Context context) { + return context.getResources() + .getInteger(R.integer.config_nightDisplayColorTemperatureMin); + } + + /** + * Returns the maximum allowed color temperature (in Kelvin) to tint the display when + * activated. + * + * @hide + */ + public static int getMaximumColorTemperature(Context context) { + return context.getResources() + .getInteger(R.integer.config_nightDisplayColorTemperatureMax); + } + + /** * Returns {@code true} if display white balance is supported by the device. * * @hide @@ -167,6 +440,25 @@ public final class ColorDisplayManager { return mManager.getTransformCapabilities(); } + /** + * Returns whether accessibility transforms are currently enabled, which determines whether + * color modes are currently configurable for this device. + * + * @hide + */ + public static boolean areAccessibilityTransformsEnabled(Context context) { + final ContentResolver cr = context.getContentResolver(); + return Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1 + || Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0) == 1; + } + + private MetricsLogger getMetricsLogger() { + if (mMetricsLogger == null) { + mMetricsLogger = new MetricsLogger(); + } + return mMetricsLogger; + } + private static class ColorDisplayManagerInternal { private static ColorDisplayManagerInternal sInstance; @@ -192,6 +484,94 @@ public final class ColorDisplayManager { } } + boolean isNightDisplayActivated() { + try { + return mCdm.isNightDisplayActivated(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayActivated(boolean activated) { + try { + return mCdm.setNightDisplayActivated(activated); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getNightDisplayColorTemperature() { + try { + return mCdm.getNightDisplayColorTemperature(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayColorTemperature(int temperature) { + try { + return mCdm.setNightDisplayColorTemperature(temperature); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getNightDisplayAutoMode() { + try { + return mCdm.getNightDisplayAutoMode(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getNightDisplayAutoModeRaw() { + try { + return mCdm.getNightDisplayAutoModeRaw(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayAutoMode(int autoMode) { + try { + return mCdm.setNightDisplayAutoMode(autoMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + Time getNightDisplayCustomStartTime() { + try { + return mCdm.getNightDisplayCustomStartTime(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayCustomStartTime(Time startTime) { + try { + return mCdm.setNightDisplayCustomStartTime(startTime); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + Time getNightDisplayCustomEndTime() { + try { + return mCdm.getNightDisplayCustomEndTime(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setNightDisplayCustomEndTime(Time endTime) { + try { + return mCdm.setNightDisplayCustomEndTime(endTime); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + boolean isDeviceColorManaged() { try { return mCdm.isDeviceColorManaged(); @@ -216,6 +596,22 @@ public final class ColorDisplayManager { } } + int getColorMode() { + try { + return mCdm.getColorMode(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + void setColorMode(int colorMode) { + try { + mCdm.setColorMode(colorMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + int getTransformCapabilities() { try { return mCdm.getTransformCapabilities(); diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl index 53cb8db8cc3d..30e76cfe2787 100644 --- a/core/java/android/hardware/display/IColorDisplayManager.aidl +++ b/core/java/android/hardware/display/IColorDisplayManager.aidl @@ -16,6 +16,8 @@ package android.hardware.display; +import android.hardware.display.Time; + /** @hide */ interface IColorDisplayManager { boolean isDeviceColorManaged(); @@ -24,4 +26,19 @@ interface IColorDisplayManager { boolean setAppSaturationLevel(String packageName, int saturationLevel); int getTransformCapabilities(); + + boolean isNightDisplayActivated(); + boolean setNightDisplayActivated(boolean activated); + int getNightDisplayColorTemperature(); + boolean setNightDisplayColorTemperature(int temperature); + int getNightDisplayAutoMode(); + int getNightDisplayAutoModeRaw(); + boolean setNightDisplayAutoMode(int autoMode); + Time getNightDisplayCustomStartTime(); + boolean setNightDisplayCustomStartTime(in Time time); + Time getNightDisplayCustomEndTime(); + boolean setNightDisplayCustomEndTime(in Time time); + + int getColorMode(); + void setColorMode(int colorMode); }
\ No newline at end of file diff --git a/core/java/android/hardware/display/Time.aidl b/core/java/android/hardware/display/Time.aidl new file mode 100644 index 000000000000..95cb5631e72f --- /dev/null +++ b/core/java/android/hardware/display/Time.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +parcelable Time;
\ No newline at end of file diff --git a/core/java/android/hardware/display/Time.java b/core/java/android/hardware/display/Time.java new file mode 100644 index 000000000000..b943ac65ab06 --- /dev/null +++ b/core/java/android/hardware/display/Time.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.time.LocalTime; + +/** + * @hide + */ +public final class Time implements Parcelable { + + private final int mHour; + private final int mMinute; + private final int mSecond; + private final int mNano; + + public Time(LocalTime localTime) { + mHour = localTime.getHour(); + mMinute = localTime.getMinute(); + mSecond = localTime.getSecond(); + mNano = localTime.getNano(); + } + + public Time(Parcel parcel) { + mHour = parcel.readInt(); + mMinute = parcel.readInt(); + mSecond = parcel.readInt(); + mNano = parcel.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int parcelableFlags) { + parcel.writeInt(mHour); + parcel.writeInt(mMinute); + parcel.writeInt(mSecond); + parcel.writeInt(mNano); + } + + public LocalTime getLocalTime() { + return LocalTime.of(mHour, mMinute, mSecond, mNano); + } + + public static final Parcelable.Creator<Time> CREATOR = new Parcelable.Creator<Time>() { + + @Override + public Time createFromParcel(Parcel source) { + return new Time(source); + } + + @Override + public Time[] newArray(int size) { + return new Time[size]; + } + }; +} diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index a98b31ad6a5e..56020b24c556 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -33,6 +34,8 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.util.List; /** @@ -106,9 +109,24 @@ public final class HdmiControlManager { public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; + @IntDef ({ + RESULT_SUCCESS, + RESULT_TIMEOUT, + RESULT_SOURCE_NOT_AVAILABLE, + RESULT_TARGET_NOT_AVAILABLE, + RESULT_ALREADY_IN_PROGRESS, + RESULT_EXCEPTION, + RESULT_INCORRECT_MODE, + RESULT_COMMUNICATION_FAILED, + }) + public @interface ControlCallbackResult {} + + /** Control operation is successfully handled by the framework. */ public static final int RESULT_SUCCESS = 0; public static final int RESULT_TIMEOUT = 1; + /** Source device that the application is using is not available. */ public static final int RESULT_SOURCE_NOT_AVAILABLE = 2; + /** Target device that the application is controlling is not available. */ public static final int RESULT_TARGET_NOT_AVAILABLE = 3; @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; @@ -394,16 +412,15 @@ public final class HdmiControlManager { /** * Gets an object that represents an HDMI-CEC logical device of type switch on the system. * - * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also - * possible to communicate with other logical devices hosted in the same system if the system is - * configured to host more than one type of HDMI-CEC logical devices. + * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus. + * It is also possible to communicate with other logical devices hosted in the same + * system if the system is configured to host more than one type of HDMI-CEC logical device. * * @return {@link HdmiSwitchClient} instance. {@code null} on failure. - * - * TODO(b/110094868): unhide for Q * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiSwitchClient getSwitchClient() { return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); @@ -412,11 +429,15 @@ public final class HdmiControlManager { /** * Get a snapshot of the real-time status of the remote devices. * - * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device. + * <p>This only applies to devices with multiple HDMI inputs. + * + * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices. An empty + * list will be returned if there is none. * - * TODO(b/110094868): unhide for Q * @hide */ + @SystemApi + @Nullable public List<HdmiDeviceInfo> getConnectedDevicesList() { try { return mService.getDeviceList(); @@ -426,14 +447,17 @@ public final class HdmiControlManager { } /** - * Power off the target device. + * Power off the target device by sending CEC commands. * - * @param deviceInfo HdmiDeviceInfo of the device to be powered off + * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. + * + * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off. * - * TODO(b/110094868): unhide for Q * @hide */ + @SystemApi public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) { + Preconditions.checkNotNull(deviceInfo); try { mService.powerOffRemoteDevice( deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); @@ -443,14 +467,16 @@ public final class HdmiControlManager { } /** - * Power on the target device. + * Power on the target device by sending CEC commands. * - * @param deviceInfo HdmiDeviceInfo of the device to be powered on + * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. + * + * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on. * - * TODO(b/110094868): unhide for Q * @hide */ public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) { + Preconditions.checkNotNull(deviceInfo); try { mService.powerOnRemoteDevice( deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); @@ -460,14 +486,17 @@ public final class HdmiControlManager { } /** - * Ask the target device to be the new Active Source. + * Request the target device to be the new Active Source by sending CEC commands. + * + * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. * * @param deviceInfo HdmiDeviceInfo of the target device * - * TODO(b/110094868): unhide for Q * @hide */ - public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) { + @SystemApi + public void requestRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) { + Preconditions.checkNotNull(deviceInfo); try { mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress()); } catch (RemoteException e) { @@ -506,8 +535,13 @@ public final class HdmiControlManager { /** * Get the physical address of the device. * + * <p>Physical address needs to be automatically adjusted when devices are phyiscally or + * electrically added or removed from the device tree. Please see HDMI Specification Version + * 1.4b 8.7 Physical Address for more details on the address discovery proccess. + * * @hide */ + @SystemApi public int getPhysicalAddress() { if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) { return mPhysicalAddress; @@ -521,16 +555,19 @@ public final class HdmiControlManager { } /** - * Check if the target device is connected to the current device. The - * API also returns true if the current device is the target. + * Check if the target remote device is connected to the current device. + * + * <p>The API also returns true if the current device is the target. * * @param targetDevice {@link HdmiDeviceInfo} of the target device. - * @return true if {@code device} is directly or indirectly connected to the + * @return true if {@code targetDevice} is directly or indirectly + * connected to the current device. * - * TODO(b/110094868): unhide for Q * @hide */ - public boolean isTargetDeviceConnected(HdmiDeviceInfo targetDevice) { + @SystemApi + public boolean isRemoteDeviceConnected(HdmiDeviceInfo targetDevice) { + Preconditions.checkNotNull(targetDevice); mPhysicalAddress = getPhysicalAddress(); if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) { return false; diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java index 1ac29736f964..a0365129a89a 100644 --- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java +++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java @@ -15,24 +15,30 @@ */ package android.hardware.hdmi; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult; +import android.os.Binder; import android.os.RemoteException; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; /** - * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which - * acts as switch. + * An {@code HdmiSwitchClient} represents a HDMI-CEC switch device. * - * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, - * but it is used by all Android TV devices that have multiple HDMI inputs, - * even if it is not a "pure" swicth and has another device type like TV or Player. + * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, but it is + * used by all Android devices that have multiple HDMI inputs, even if it is not a "pure" swicth + * and has another device type like TV or Player. * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ +@SystemApi public class HdmiSwitchClient extends HdmiClient { private static final String TAG = "HdmiSwitchClient"; @@ -41,17 +47,15 @@ public class HdmiSwitchClient extends HdmiClient { super(service); } - private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) { + private static IHdmiControlCallback getCallbackWrapper(final OnSelectListener listener) { return new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { - callback.onComplete(result); + listener.onSelect(result); } }; } - /** @hide */ - // TODO(b/110094868): unhide for Q @Override public int getDeviceType() { return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH; @@ -61,20 +65,17 @@ public class HdmiSwitchClient extends HdmiClient { * Selects a CEC logical device to be a new active source. * * @param logicalAddress logical address of the device to select - * @param callback callback to get the result with - * @throws {@link IllegalArgumentException} if the {@code callback} is null + * @param listener listener to get the result with * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ - public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback must not be null."); - } + public void selectDevice(int logicalAddress, @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); try { - mService.deviceSelect(logicalAddress, getCallbackWrapper(callback)); + mService.deviceSelect(logicalAddress, getCallbackWrapper(listener)); } catch (RemoteException e) { Log.e(TAG, "failed to select device: ", e); + throw e.rethrowFromSystemServer(); } } @@ -82,20 +83,83 @@ public class HdmiSwitchClient extends HdmiClient { * Selects a HDMI port to be a new route path. * * @param portId HDMI port to select - * @param callback callback to get the result with - * @throws {@link IllegalArgumentException} if the {@code callback} is null + * @see {@link android.media.tv.TvInputHardwareInfo#getHdmiPortId()} + * to get portId of a specific TV Input. + * @param listener listener to get the result with * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ - public void portSelect(int portId, @NonNull SelectCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("Callback must not be null"); + @SystemApi + public void selectPort(int portId, @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); + try { + mService.portSelect(portId, getCallbackWrapper(listener)); + } catch (RemoteException e) { + Log.e(TAG, "failed to select port: ", e); + throw e.rethrowFromSystemServer(); } + } + + /** + * Selects a CEC logical device to be a new active source. + * + * @param logicalAddress logical address of the device to select + * @param executor executor to allow the develper to specify the thread upon which the listeners + * will be invoked + * @param listener listener to get the result with + * + * @hide + */ + public void selectDevice( + int logicalAddress, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); + try { + mService.deviceSelect(logicalAddress, + new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + Binder.withCleanCallingIdentity( + () -> executor.execute(() -> listener.onSelect(result))); + } + } + ); + } catch (RemoteException e) { + Log.e(TAG, "failed to select device: ", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Selects a HDMI port to be a new route path. + * + * @param portId HDMI port to select + * @param executor executor to allow the develper to specify the thread upon which the listeners + * will be invoked + * @param listener listener to get the result with + * + * @hide + */ + @SystemApi + public void selectPort( + int portId, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnSelectListener listener) { + Preconditions.checkNotNull(listener); try { - mService.portSelect(portId, getCallbackWrapper(callback)); + mService.portSelect(portId, + new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + Binder.withCleanCallingIdentity( + () -> executor.execute(() -> listener.onSelect(result))); + } + } + ); } catch (RemoteException e) { Log.e(TAG, "failed to select port: ", e); + throw e.rethrowFromSystemServer(); } } @@ -105,10 +169,9 @@ public class HdmiSwitchClient extends HdmiClient { * <p>This only applies to device with multiple HDMI inputs * * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if - * there is none. + * there is none. * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ public List<HdmiDeviceInfo> getDeviceList() { try { @@ -120,18 +183,19 @@ public class HdmiSwitchClient extends HdmiClient { } /** - * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}. + * Listener interface used to get the result of {@link #deviceSelect} or {@link #portSelect}. * * @hide - * TODO(b/110094868): unhide and add @SystemApi for Q */ - public interface SelectCallback { + @SystemApi + public interface OnSelectListener { /** * Called when the operation is finished. * - * @param result the result value of {@link #deviceSelect} or {@link #portSelect}. + * @param result callback result. + * @see {@link ControlCallbackResult} */ - void onComplete(int result); + void onSelect(@ControlCallbackResult int result); } } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 103069474445..37b25c8fec0c 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -179,19 +179,24 @@ class IInputMethodWrapper extends IInputMethod.Stub return; case DO_START_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; - final int missingMethods = msg.arg1; - final boolean restarting = msg.arg2 != 0; final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4; + SomeArgs moreArgs = (SomeArgs) args.arg5; final InputConnection ic = inputContext != null ? new InputConnectionWrapper( - mTarget, inputContext, missingMethods, isUnbindIssued) : null; + mTarget, inputContext, moreArgs.argi3, isUnbindIssued) + : null; info.makeCompatible(mTargetSdkVersion); - inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */, - startInputToken); + inputMethod.dispatchStartInputWithToken( + ic, + info, + moreArgs.argi1 == 1 /* restarting */, + startInputToken, + moreArgs.argi2 == 1 /* shouldPreRenderIme */); args.recycle(); + moreArgs.recycle(); return; } case DO_CREATE_SESSION: { @@ -291,14 +296,17 @@ class IInputMethodWrapper extends IInputMethod.Stub @Override public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, - EditorInfo attribute, boolean restarting) { + EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) { if (mIsUnbindIssued == null) { Log.e(TAG, "startInput must be called after bindInput."); mIsUnbindIssued = new AtomicBoolean(); } - mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT, - missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute, - mIsUnbindIssued)); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = restarting ? 1 : 0; + args.argi2 = shouldPreRenderIme ? 1 : 0; + args.argi3 = missingMethods; + mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO( + DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 38ddc169f527..5b3ad77dbf43 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -346,6 +346,13 @@ public class InputMethodService extends AbstractInputMethodService { */ public static final int IME_VISIBLE = 0x2; + /** + * @hide + * The IME is active and ready with views but set invisible. + * This flag cannot be combined with {@link #IME_VISIBLE}. + */ + public static final int IME_INVISIBLE = 0x4; + // Min and max values for back disposition. private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING; @@ -362,10 +369,19 @@ public class InputMethodService extends AbstractInputMethodService { View mRootView; SoftInputWindow mWindow; boolean mInitialized; - boolean mWindowCreated; - boolean mWindowVisible; - boolean mWindowWasVisible; + boolean mViewsCreated; + // IME views visibility. + boolean mDecorViewVisible; + boolean mDecorViewWasVisible; boolean mInShowWindow; + // True if pre-rendering of IME views/window is supported. + boolean mCanPreRender; + // If IME is pre-rendered. + boolean mIsPreRendered; + // IME window visibility. + // Use (mDecorViewVisible && mWindowVisible) to check if IME is visible to the user. + boolean mWindowVisible; + ViewGroup mFullscreenArea; FrameLayout mExtractFrame; FrameLayout mCandidatesFrame; @@ -552,15 +568,18 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, + public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + @NonNull IBinder startInputToken, boolean shouldPreRenderIme) { mPrivOps.reportStartInput(startInputToken); - // This needs to be dispatched to interface methods rather than doStartInput(). - // Otherwise IME developers who have overridden those interface methods will lose - // notifications. - super.dispatchStartInputWithToken(inputConnection, editorInfo, restarting, - startInputToken); + mCanPreRender = shouldPreRenderIme; + if (DEBUG) Log.v(TAG, "Will Pre-render IME: " + mCanPreRender); + + if (restarting) { + restartInput(inputConnection, editorInfo); + } else { + startInput(inputConnection, editorInfo); + } } /** @@ -570,14 +589,27 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "hideSoftInput()"); - boolean wasVis = isInputViewShown(); - mShowInputFlags = 0; - mShowInputRequested = false; - doHideWindow(); + final boolean wasVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + if (mIsPreRendered) { + // TODO: notify visibility to insets consumer. + if (DEBUG) { + Log.v(TAG, "Making IME window invisible"); + } + setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); + onPreRenderedWindowVisibilityChanged(false /* setVisible */); + } else { + mShowInputFlags = 0; + mShowInputRequested = false; + doHideWindow(); + } + final boolean isVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { - resultReceiver.send(wasVis != isInputViewShown() + resultReceiver.send(visibilityChanged ? InputMethodManager.RESULT_HIDDEN - : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); } } @@ -589,17 +621,28 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void showSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "showSoftInput()"); - boolean wasVis = isInputViewShown(); + final boolean wasVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { - showWindow(true); + if (mIsPreRendered) { + // TODO: notify visibility to insets consumer. + if (DEBUG) { + Log.v(TAG, "Making IME window visible"); + } + onPreRenderedWindowVisibilityChanged(true /* setVisible */); + } else { + showWindow(true); + } } // If user uses hard keyboard, IME button should always be shown. - setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); - + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); + final boolean isVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { - resultReceiver.send(wasVis != isInputViewShown() + resultReceiver.send(visibilityChanged ? InputMethodManager.RESULT_SHOWN - : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); } } @@ -972,7 +1015,7 @@ public class InputMethodService extends AbstractInputMethodService { void initViews() { mInitialized = false; - mWindowCreated = false; + mViewsCreated = false; mShowInputRequested = false; mShowInputFlags = 0; @@ -1046,7 +1089,7 @@ public class InputMethodService extends AbstractInputMethodService { } private void resetStateForNewConfiguration() { - boolean visible = mWindowVisible; + boolean visible = mDecorViewVisible; int showFlags = mShowInputFlags; boolean showingInput = mShowInputRequested; CompletionInfo[] completions = mCurCompletions; @@ -1129,7 +1172,7 @@ public class InputMethodService extends AbstractInputMethodService { return; } mBackDisposition = disposition; - setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); } /** @@ -1380,7 +1423,7 @@ public class InputMethodService extends AbstractInputMethodService { mExtractFrame.setVisibility(View.GONE); } updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE); - if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) { + if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) { int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation : com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation, @@ -1439,7 +1482,7 @@ public class InputMethodService extends AbstractInputMethodService { */ public void updateInputViewShown() { boolean isShown = mShowInputRequested && onEvaluateInputViewShown(); - if (mIsInputViewShown != isShown && mWindowVisible) { + if (mIsInputViewShown != isShown && mDecorViewVisible) { mIsInputViewShown = isShown; mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE); if (mInputView == null) { @@ -1458,14 +1501,14 @@ public class InputMethodService extends AbstractInputMethodService { public boolean isShowInputRequested() { return mShowInputRequested; } - + /** * Return whether the soft input view is <em>currently</em> shown to the * user. This is the state that was last determined and * applied by {@link #updateInputViewShown()}. */ public boolean isInputViewShown() { - return mIsInputViewShown && mWindowVisible; + return mCanPreRender ? mWindowVisible : mIsInputViewShown && mDecorViewVisible; } /** @@ -1499,7 +1542,7 @@ public class InputMethodService extends AbstractInputMethodService { */ public void setCandidatesViewShown(boolean shown) { updateCandidatesVisibility(shown); - if (!mShowInputRequested && mWindowVisible != shown) { + if (!mShowInputRequested && mDecorViewVisible != shown) { // If we are being asked to show the candidates view while the app // has not asked for the input view to be shown, then we need // to update whether the window is shown. @@ -1804,7 +1847,8 @@ public class InputMethodService extends AbstractInputMethodService { public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested - + " mWindowCreated=" + mWindowCreated + + " mViewsCreated=" + mViewsCreated + + " mDecorViewVisible=" + mDecorViewVisible + " mWindowVisible=" + mWindowVisible + " mInputStarted=" + mInputStarted + " mShowInputFlags=" + mShowInputFlags); @@ -1814,18 +1858,42 @@ public class InputMethodService extends AbstractInputMethodService { return; } - mWindowWasVisible = mWindowVisible; + mDecorViewWasVisible = mDecorViewVisible; mInShowWindow = true; - showWindowInner(showInput); - mWindowWasVisible = true; + boolean isPreRenderedAndInvisible = mIsPreRendered && !mWindowVisible; + final int previousImeWindowStatus = + (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown() + ? (isPreRenderedAndInvisible ? IME_INVISIBLE : IME_VISIBLE) : 0); + startViews(prepareWindow(showInput)); + final int nextImeWindowStatus = mapToImeWindowStatus(); + if (previousImeWindowStatus != nextImeWindowStatus) { + setImeWindowStatus(nextImeWindowStatus, mBackDisposition); + } + + // compute visibility + onWindowShown(); + mIsPreRendered = mCanPreRender; + if (mIsPreRendered) { + onPreRenderedWindowVisibilityChanged(true /* setVisible */); + } else { + // Pre-rendering not supported. + if (DEBUG) Log.d(TAG, "No pre-rendering supported"); + mWindowVisible = true; + } + + // request draw for the IME surface. + // When IME is not pre-rendered, this will actually show the IME. + if ((previousImeWindowStatus & IME_ACTIVE) == 0) { + if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); + mWindow.show(); + } + mDecorViewWasVisible = true; mInShowWindow = false; } - void showWindowInner(boolean showInput) { + private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; - final int previousImeWindowStatus = - (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0); - mWindowVisible = true; + mDecorViewVisible = true; if (!mShowInputRequested && mInputStarted && showInput) { doShowInput = true; mShowInputRequested = true; @@ -1836,8 +1904,8 @@ public class InputMethodService extends AbstractInputMethodService { updateFullscreenMode(); updateInputViewShown(); - if (!mWindowCreated) { - mWindowCreated = true; + if (!mViewsCreated) { + mViewsCreated = true; initialize(); if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); View v = onCreateCandidatesView(); @@ -1846,6 +1914,10 @@ public class InputMethodService extends AbstractInputMethodService { setCandidatesView(v); } } + return doShowInput; + } + + private void startViews(boolean doShowInput) { if (mShowInputRequested) { if (!mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); @@ -1857,29 +1929,26 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = true; onStartCandidatesView(mInputEditorInfo, false); } - - if (doShowInput) { - startExtractingText(false); - } + if (doShowInput) startExtractingText(false); + } - final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown()); - if (previousImeWindowStatus != nextImeWindowStatus) { - setImeWindowStatus(nextImeWindowStatus, mBackDisposition); - } - if ((previousImeWindowStatus & IME_ACTIVE) == 0) { - if (DEBUG) Log.v(TAG, "showWindow: showing!"); + private void onPreRenderedWindowVisibilityChanged(boolean setVisible) { + mWindowVisible = setVisible; + mShowInputFlags = setVisible ? mShowInputFlags : 0; + mShowInputRequested = setVisible; + mDecorViewVisible = setVisible; + if (setVisible) { onWindowShown(); - mWindow.show(); } } - private void finishViews() { + private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); - onFinishInputView(false); + onFinishInputView(finishingInput); } else if (mCandidatesViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); - onFinishCandidatesView(false); + onFinishCandidatesView(finishingInput); } mInputViewStarted = false; mCandidatesViewStarted = false; @@ -1891,12 +1960,15 @@ public class InputMethodService extends AbstractInputMethodService { } public void hideWindow() { - finishViews(); - if (mWindowVisible) { + if (DEBUG) Log.v(TAG, "CALL: hideWindow"); + mIsPreRendered = false; + mWindowVisible = false; + finishViews(false /* finishingInput */); + if (mDecorViewVisible) { mWindow.hide(); - mWindowVisible = false; + mDecorViewVisible = false; onWindowHidden(); - mWindowWasVisible = false; + mDecorViewWasVisible = false; } updateFullscreenMode(); } @@ -1956,15 +2028,8 @@ public class InputMethodService extends AbstractInputMethodService { } void doFinishInput() { - if (mInputViewStarted) { - if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); - onFinishInputView(true); - } else if (mCandidatesViewStarted) { - if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); - onFinishCandidatesView(true); - } - mInputViewStarted = false; - mCandidatesViewStarted = false; + if (DEBUG) Log.v(TAG, "CALL: doFinishInput"); + finishViews(true /* finishingInput */); if (mInputStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInput"); onFinishInput(); @@ -1984,7 +2049,7 @@ public class InputMethodService extends AbstractInputMethodService { initialize(); if (DEBUG) Log.v(TAG, "CALL: onStartInput"); onStartInput(attribute, restarting); - if (mWindowVisible) { + if (mDecorViewVisible) { if (mShowInputRequested) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); mInputViewStarted = true; @@ -1995,6 +2060,31 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = true; onStartCandidatesView(mInputEditorInfo, restarting); } + } else if (mCanPreRender && mInputEditorInfo != null && mStartedInputConnection != null) { + // Pre-render IME views and window when real EditorInfo is available. + // pre-render IME window and keep it invisible. + if (DEBUG) Log.v(TAG, "Pre-Render IME for " + mInputEditorInfo.fieldName); + if (mInShowWindow) { + Log.w(TAG, "Re-entrance in to showWindow"); + return; + } + + mDecorViewWasVisible = mDecorViewVisible; + mInShowWindow = true; + startViews(prepareWindow(true /* showInput */)); + + // compute visibility + mIsPreRendered = true; + onPreRenderedWindowVisibilityChanged(false /* setVisible */); + + // request draw for the IME surface. + // When IME is not pre-rendered, this will actually show the IME. + if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); + mWindow.show(); + mDecorViewWasVisible = true; + mInShowWindow = false; + } else { + mIsPreRendered = false; } } @@ -2153,7 +2243,7 @@ public class InputMethodService extends AbstractInputMethodService { // consume the back key. if (doIt) requestHideSelf(0); return true; - } else if (mWindowVisible) { + } else if (mDecorViewVisible) { if (mCandidatesVisibility == View.VISIBLE) { // If we are showing candidates even if no input area, then // hide them. @@ -2180,7 +2270,6 @@ public class InputMethodService extends AbstractInputMethodService { return mExtractEditText; } - /** * Called back when a {@link KeyEvent} is forwarded from the target application. * @@ -2893,8 +2982,11 @@ public class InputMethodService extends AbstractInputMethodService { inputContentInfo.setUriToken(uriToken); } - private static int mapToImeWindowStatus(boolean isInputViewShown) { - return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0); + private int mapToImeWindowStatus() { + return IME_ACTIVE + | (isInputViewShown() + ? (mCanPreRender ? (mWindowVisible ? IME_VISIBLE : IME_INVISIBLE) + : IME_VISIBLE) : 0); } /** @@ -2904,9 +2996,10 @@ public class InputMethodService extends AbstractInputMethodService { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method service state for " + this + ":"); - p.println(" mWindowCreated=" + mWindowCreated); - p.println(" mWindowVisible=" + mWindowVisible - + " mWindowWasVisible=" + mWindowWasVisible + p.println(" mViewsCreated=" + mViewsCreated); + p.println(" mDecorViewVisible=" + mDecorViewVisible + + " mDecorViewWasVisible=" + mDecorViewWasVisible + + " mWindowVisible=" + mWindowVisible + " mInShowWindow=" + mInShowWindow); p.println(" Configuration=" + getResources().getConfiguration()); p.println(" mToken=" + mToken); @@ -2926,6 +3019,8 @@ public class InputMethodService extends AbstractInputMethodService { p.println(" mShowInputRequested=" + mShowInputRequested + " mLastShowInputRequested=" + mLastShowInputRequested + + " mCanPreRender=" + mCanPreRender + + " mIsPreRendered=" + mIsPreRendered + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags)); p.println(" mCandidatesVisibility=" + mCandidatesVisibility + " mFullscreenApplied=" + mFullscreenApplied diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java index e926fda336f4..ef2269a145d0 100644 --- a/core/java/android/net/ProxyInfo.java +++ b/core/java/android/net/ProxyInfo.java @@ -39,12 +39,12 @@ import java.util.Locale; */ public class ProxyInfo implements Parcelable { - private String mHost; - private int mPort; - private String mExclusionList; - private String[] mParsedExclusionList; + private final String mHost; + private final int mPort; + private final String mExclusionList; + private final String[] mParsedExclusionList; + private final Uri mPacFileUrl; - private Uri mPacFileUrl; /** *@hide */ @@ -96,7 +96,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String host, int port, String exclList) { mHost = host; mPort = port; - setExclusionList(exclList); + mExclusionList = exclList; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.EMPTY; } @@ -107,7 +108,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } @@ -121,7 +123,8 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(String pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); mPacFileUrl = Uri.parse(pacFileUrl); } @@ -132,13 +135,22 @@ public class ProxyInfo implements Parcelable { public ProxyInfo(Uri pacFileUrl, int localProxyPort) { mHost = LOCAL_HOST; mPort = localProxyPort; - setExclusionList(LOCAL_EXCL_LIST); + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); if (pacFileUrl == null) { throw new NullPointerException(); } mPacFileUrl = pacFileUrl; } + private static String[] parseExclusionList(String exclusionList) { + if (exclusionList == null) { + return new String[0]; + } else { + return exclusionList.toLowerCase(Locale.ROOT).split(","); + } + } + private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; @@ -159,6 +171,10 @@ public class ProxyInfo implements Parcelable { mExclusionList = source.getExclusionListAsString(); mParsedExclusionList = source.mParsedExclusionList; } else { + mHost = null; + mPort = 0; + mExclusionList = null; + mParsedExclusionList = null; mPacFileUrl = Uri.EMPTY; } } @@ -214,24 +230,14 @@ public class ProxyInfo implements Parcelable { return mExclusionList; } - // comma separated - private void setExclusionList(String exclusionList) { - mExclusionList = exclusionList; - if (mExclusionList == null) { - mParsedExclusionList = new String[0]; - } else { - mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(","); - } - } - /** * @hide */ public boolean isValid() { if (!Uri.EMPTY.equals(mPacFileUrl)) return true; return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, - mPort == 0 ? "" : Integer.toString(mPort), - mExclusionList == null ? "" : mExclusionList); + mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); } /** @@ -262,7 +268,7 @@ public class ProxyInfo implements Parcelable { sb.append("] "); sb.append(Integer.toString(mPort)); if (mExclusionList != null) { - sb.append(" xl=").append(mExclusionList); + sb.append(" xl=").append(mExclusionList); } } else { sb.append("[ProxyProperties.mHost == null]"); @@ -308,8 +314,8 @@ public class ProxyInfo implements Parcelable { */ public int hashCode() { return ((null == mHost) ? 0 : mHost.hashCode()) - + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) - + mPort; + + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) + + mPort; } /** @@ -352,8 +358,7 @@ public class ProxyInfo implements Parcelable { } String exclList = in.readString(); String[] parsedExclList = in.readStringArray(); - ProxyInfo proxyProperties = - new ProxyInfo(host, port, exclList, parsedExclList); + ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); return proxyProperties; } diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 37bf3a71ce62..dc099a46aa2a 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -509,6 +509,15 @@ public class VpnService extends Service { } /** + * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation + * and it is possible that some apps will ignore it. + */ + public Builder setHttpProxy(ProxyInfo proxyInfo) { + mConfig.proxyInfo = proxyInfo; + return this; + } + + /** * Add a network address to the VPN interface. Both IPv4 and IPv6 * addresses are supported. At least one address must be set before * calling {@link #establish}. diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 518528dd1a5b..d463b4422847 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -75,13 +75,25 @@ public class BugreportManager { int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; + /** The request to get user consent timed out. */ + int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = + IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; + /** * Called when taking bugreport resulted in an error. * * @param errorCode the error that occurred. Possible values are * {@code BUGREPORT_ERROR_INVALID_INPUT}, * {@code BUGREPORT_ERROR_RUNTIME}, - * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}. + * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}, + * {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}. + * + * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not + * consent to sharing the bugreport with the calling app. + * + * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed + * out, but the bugreport could be available in the internal directory of dumpstate for + * manual retrieval. */ void onError(@BugreportErrorCode int errorCode); diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 5bf909566f3b..efcad3ece97d 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -369,26 +369,6 @@ public class GraphicsEnvironment { } /** - * Attempt to setup ANGLE with a (temporary) default rules file: b/121153494 - * True: Rules file was loaded. - * False: Rules file was *not* loaded. - */ - private boolean setupAngleRulesDebug(String packageName, String paths, String devOptIn) { - // b/121153494 - // Skip APK rules file checking. - if (!DEBUG) { - Log.v(TAG, "Skipping loading the rules file."); - // Fill in some default values for now, so the loader can get an answer when it asks. - // Most importantly, we need to indicate which app we are init'ing and what the - // developer options for it are so we can turn on ANGLE if needed. - setAngleInfo(paths, packageName, devOptIn, null, 0, 0); - return true; - } - - return false; - } - - /** * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK. * True: APK rules file was loaded. * False: APK rules file was *not* loaded. @@ -425,16 +405,57 @@ public class GraphicsEnvironment { } /** + * Pull ANGLE whitelist from GlobalSettings and compare against current package + */ + private boolean checkAngleWhitelist(Bundle bundle, String packageName) { + List<String> angleWhitelist = + getGlobalSettingsString(bundle, + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST); + + return angleWhitelist.contains(packageName); + } + + /** * Pass ANGLE details down to trigger enable logic */ public void setupAngle(Context context, Bundle bundle, String packageName) { - String devOptIn = getDriverForPkg(bundle, packageName); + if (packageName.isEmpty()) { + Log.v(TAG, "No package name available yet, skipping ANGLE setup"); + return; + } + String devOptIn = getDriverForPkg(bundle, packageName); if (DEBUG) { Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + "set to: '" + devOptIn + "'"); } + // We only need to check rules if the app is whitelisted or the developer has + // explicitly chosen something other than default driver. + // + // The whitelist will be generated by the ANGLE APK at both boot time and + // ANGLE update time. It will only include apps mentioned in the rules file. + // + // If the user has set the developer option to something other than default, + // we need to call setupAngleRulesApk() with the package name and the developer + // option value (native/angle/other). Then later when we are actually trying to + // load a driver, GraphicsEnv::shouldUseAngle() has seen the package name before + // and can confidently answer yes/no based on the previously set developer + // option value. + boolean whitelisted = checkAngleWhitelist(bundle, packageName); + boolean defaulted = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.DEFAULT)); + boolean rulesCheck = (whitelisted || !defaulted); + if (!rulesCheck) { + return; + } + + if (whitelisted) { + Log.v(TAG, "ANGLE whitelist includes " + packageName); + } + if (!defaulted) { + Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn); + } + String anglePkgName = getAnglePackageName(context); if (anglePkgName.isEmpty()) { Log.e(TAG, "Failed to find ANGLE package."); @@ -466,12 +487,6 @@ public class GraphicsEnvironment { return; } - // b/121153494 - if (setupAngleRulesDebug(packageName, paths, devOptIn)) { - // We setup ANGLE with defaults, so we're done here. - return; - } - if (setupAngleRulesApk(anglePkgName, angleInfo, context, packageName, paths, devOptIn)) { // We setup ANGLE with rules from the APK, so we're done here. return; diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 63912ec327a4..630bd2e509ff 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -54,6 +54,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InterruptedIOException; +import java.io.UncheckedIOException; import java.net.DatagramSocket; import java.net.Socket; import java.nio.ByteOrder; @@ -393,26 +394,41 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * @param socket The Socket whose FileDescriptor is used to create * a new ParcelFileDescriptor. * - * @return A new ParcelFileDescriptor with the FileDescriptor of the - * specified Socket. + * @return A new ParcelFileDescriptor with a duped copy of the + * FileDescriptor of the specified Socket. + * + * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException. */ public static ParcelFileDescriptor fromSocket(Socket socket) { FileDescriptor fd = socket.getFileDescriptor$(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + return fd != null ? ParcelFileDescriptor.dup(fd) : null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** - * Create a new ParcelFileDescriptor from the specified DatagramSocket. + * Create a new ParcelFileDescriptor from the specified DatagramSocket. The + * new ParcelFileDescriptor holds a dup of the original FileDescriptor in + * the DatagramSocket, so you must still close the DatagramSocket as well + * as the new ParcelFileDescriptor. * * @param datagramSocket The DatagramSocket whose FileDescriptor is used * to create a new ParcelFileDescriptor. * - * @return A new ParcelFileDescriptor with the FileDescriptor of the - * specified DatagramSocket. + * @return A new ParcelFileDescriptor with a duped copy of the + * FileDescriptor of the specified Socket. + * + * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException. */ public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) { FileDescriptor fd = datagramSocket.getFileDescriptor$(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + return fd != null ? ParcelFileDescriptor.dup(fd) : null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -542,7 +558,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } file.deactivate(); FileDescriptor fd = file.getFileDescriptor(); - return fd != null ? new ParcelFileDescriptor(fd) : null; + return fd != null ? ParcelFileDescriptor.dup(fd) : null; } /** diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 01d85c6c6c85..99fb608b80dc 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.ContentResolver; @@ -25,6 +26,8 @@ import android.hardware.vibrator.V1_2.Effect; import android.net.Uri; import android.util.MathUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -52,26 +55,20 @@ public abstract class VibrationEffect implements Parcelable { * A click effect. * * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_CLICK = Effect.CLICK; /** * A double click effect. * * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK; /** * A tick effect. * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_TICK = Effect.TICK; /** @@ -93,9 +90,7 @@ public abstract class VibrationEffect implements Parcelable { /** * A heavy click effect. * @see #get(int) - * @hide */ - @TestApi public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK; /** {@hide} */ @@ -136,6 +131,16 @@ public abstract class VibrationEffect implements Parcelable { Effect.RINGTONE_15 }; + /** @hide */ + @IntDef(prefix = { "EFFECT_" }, value = { + EFFECT_TICK, + EFFECT_CLICK, + EFFECT_HEAVY_CLICK, + EFFECT_DOUBLE_CLICK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EffectType {} + /** @hide to prevent subclassing from outside of the framework */ public VibrationEffect() { } @@ -219,6 +224,27 @@ public abstract class VibrationEffect implements Parcelable { } /** + * Create a predefined vibration effect. + * + * Predefined effects are a set of common vibration effects that should be identical, regardless + * of the app they come from, in order to provide a cohesive experience for users across + * the entire device. They also may be custom tailored to the device hardware in order to + * provide a better experience than you could otherwise build using the generic building + * blocks. + * + * This will fallback to a generic pattern if one exists and there does not exist a + * hardware-specific implementation of the effect. + * + * @param effectId The ID of the effect to perform: + * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} + * + * @return The desired effect. + */ + public static VibrationEffect createPrebaked(@EffectType int effectId) { + return get(effectId, true); + } + + /** * Get a predefined vibration effect. * * Predefined effects are a set of common vibration effects that should be identical, regardless diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 249b622dac4d..5dd869f46b4e 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -35,4 +35,6 @@ oneway interface IPermissionController { void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted, boolean countSystem, in RemoteCallback callback); void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback); + void isApplicationQualifiedForRole(String roleName, String packageName, + in RemoteCallback callback); } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index bfcca7c10151..b59d0c7a660e 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -21,6 +21,7 @@ import static android.permission.PermissionControllerService.SERVICE_INTERFACE; import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.internal.util.Preconditions.checkStringNotEmpty; import android.Manifest; import android.annotation.CallbackExecutor; @@ -343,6 +344,28 @@ public final class PermissionControllerManager { } /** + * Check whether an application is qualified for a role. + * + * @param roleName name of the role to check for + * @param packageName package name of the application to check for + * @param executor Executor on which to invoke the callback + * @param callback Callback to receive the result + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) + public void isApplicationQualifiedForRole(@NonNull String roleName, @NonNull String packageName, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + checkStringNotEmpty(roleName); + checkStringNotEmpty(packageName); + checkNotNull(executor); + checkNotNull(callback); + + sRemoteService.scheduleRequest(new PendingIsApplicationQualifiedForRoleRequest( + sRemoteService, roleName, packageName, executor, callback)); + } + + /** * A connection to the remote service */ static final class RemoteService extends @@ -810,4 +833,58 @@ public final class PermissionControllerManager { } } } + + /** + * Request for {@link #isApplicationQualifiedForRole}. + */ + private static final class PendingIsApplicationQualifiedForRoleRequest extends + AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> { + + private final @NonNull String mRoleName; + private final @NonNull String mPackageName; + private final @NonNull Consumer<Boolean> mCallback; + + private final @NonNull RemoteCallback mRemoteCallback; + + private PendingIsApplicationQualifiedForRoleRequest(@NonNull RemoteService service, + @NonNull String roleName, @NonNull String packageName, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + super(service); + + mRoleName = roleName; + mPackageName = packageName; + mCallback = callback; + + mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { + long token = Binder.clearCallingIdentity(); + try { + boolean qualified; + if (result != null) { + qualified = result.getBoolean(KEY_RESULT); + } else { + qualified = false; + } + callback.accept(qualified); + } finally { + Binder.restoreCallingIdentity(token); + finish(); + } + }), null); + } + + @Override + protected void onTimeout(RemoteService remoteService) { + mCallback.accept(false); + } + + @Override + public void run() { + try { + getService().getServiceInterface().isApplicationQualifiedForRole(mRoleName, + mPackageName, mRemoteCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error checking whether application qualifies for role", e); + } + } + } } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 10e8c8d5b8dd..9a58b971baf5 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.internal.util.Preconditions.checkStringNotEmpty; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; @@ -136,8 +137,19 @@ public abstract class PermissionControllerService extends Service { * * @return descriptions of the users of permissions */ - public abstract @NonNull List<RuntimePermissionUsageInfo> - onPermissionUsageResult(boolean countSystem, long numMillis); + public abstract @NonNull List<RuntimePermissionUsageInfo> onGetPermissionUsages( + boolean countSystem, long numMillis); + + /** + * Check whether an application is qualified for a role. + * + * @param roleName name of the role to check for + * @param packageName package name of the application to check for + * + * @return whether the application is qualified for the role. + */ + public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName, + @NonNull String packageName); @Override public final IBinder onBind(Intent intent) { @@ -240,6 +252,20 @@ public abstract class PermissionControllerService extends Service { PermissionControllerService.this, countSystem, numMillis, callback)); } + + @Override + public void isApplicationQualifiedForRole(String roleName, String packageName, + RemoteCallback callback) { + checkStringNotEmpty(roleName); + checkStringNotEmpty(packageName); + checkNotNull(callback, "callback"); + + enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null); + + mHandler.sendMessage(obtainMessage( + PermissionControllerService::isApplicationQualifiedForRole, + PermissionControllerService.this, roleName, packageName, callback)); + } }; } @@ -296,7 +322,7 @@ public abstract class PermissionControllerService extends Service { private void getPermissionUsages(boolean countSystem, long numMillis, @NonNull RemoteCallback callback) { List<RuntimePermissionUsageInfo> users = - onPermissionUsageResult(countSystem, numMillis); + onGetPermissionUsages(countSystem, numMillis); if (users != null && !users.isEmpty()) { Bundle result = new Bundle(); result.putParcelableList(PermissionControllerManager.KEY_RESULT, users); @@ -305,4 +331,12 @@ public abstract class PermissionControllerService extends Service { callback.sendResult(null); } } + + private void isApplicationQualifiedForRole(@NonNull String roleName, + @NonNull String packageName, @NonNull RemoteCallback callback) { + boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName); + Bundle result = new Bundle(); + result.putBoolean(PermissionControllerManager.KEY_RESULT, qualified); + callback.sendResult(result); + } } diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index c167ea18f0c5..8bd75d779154 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -44,6 +44,8 @@ import android.util.Log; import com.android.internal.util.Preconditions; +import java.util.Set; + /** * <p> * The contract between the calendar provider and applications. Contains @@ -217,7 +219,7 @@ public final class CalendarContract { * The intent will have its action set to * {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras * corresponding to the API's arguments. A calendar app intending to support - * cross profile events viewing should handle this intent, parse the arguments + * cross-profile events viewing should handle this intent, parse the arguments * and show the appropriate UI. * * @param context the context. @@ -767,9 +769,10 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is * queried from a package that is not whitelisted by profile owner of the managed profile - * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * via + * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED @@ -1758,9 +1761,10 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is * queried from a package that is not whitelisted by profile owner of the managed profile - * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * via + * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED @@ -1968,10 +1972,10 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a managed profile, or cross profile calendar for the managed profile is disabled in + * of a managed profile, or cross-profile calendar for the managed profile is disabled in * Settings, or this uri is queried from a package that is not whitelisted by * profile owner of the managed profile via - * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6ccd2968824e..148dd910ef69 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -153,6 +153,51 @@ public final class DeviceConfig { String OOB_WHITELIST = "oob_whitelist"; } + /** + * Namespace for activity manager related features. These features will be applied + * immediately upon change. + * + * @hide + */ + @SystemApi + public interface ActivityManager { + String NAMESPACE = "activity_manager"; + + /** + * App compaction flags. See {@link com.android.server.am.AppCompactor}. + */ + String KEY_USE_COMPACTION = "use_compaction"; + String KEY_COMPACT_ACTION_1 = "compact_action_1"; + String KEY_COMPACT_ACTION_2 = "compact_action_2"; + String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; + String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; + String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; + String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; + + /** + * Maximum number of cached processes. See + * {@link com.android.server.am.ActivityManagerConstants}. + */ + String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; + } + + /** + * Namespace for storage-related features. + * + * @hide + */ + @SystemApi + public interface Storage { + String NAMESPACE = "storage"; + + /** + * If {@code 1}, enables the isolated storage feature. If {@code -1}, + * disables the isolated storage feature. If {@code 0}, uses the default + * value from the build system. + */ + String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled"; + } + private static final Object sLock = new Object(); @GuardedBy("sLock") private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bdeacdf91fe3..5222fc36710f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -59,6 +59,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.database.SQLException; +import android.hardware.display.ColorDisplayManager; import android.location.LocationManager; import android.media.AudioFormat; import android.net.ConnectivityManager; @@ -89,7 +90,6 @@ import android.util.MemoryIntArray; import android.view.inputmethod.InputMethodSystemProperty; import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.ColorDisplayController; import com.android.internal.widget.ILockSettings; import java.io.IOException; @@ -3239,8 +3239,8 @@ public final class Settings { private static final Validator DISPLAY_COLOR_MODE_VALIDATOR = new SettingsValidators.InclusiveIntegerRangeValidator( - ColorDisplayController.COLOR_MODE_NATURAL, - ColorDisplayController.COLOR_MODE_AUTOMATIC); + ColorDisplayManager.COLOR_MODE_NATURAL, + ColorDisplayManager.COLOR_MODE_AUTOMATIC); /** * The amount of time in milliseconds before the device goes to sleep or begins @@ -7802,6 +7802,9 @@ public final class Settings { * or an activity that handles ACTION_ASSIST, or empty which means using the default * handling. * + * <p>This should be set indirectly by setting the {@link + * android.app.role.RoleManager#ROLE_ASSISTANT assistant role}. + * * @hide */ @UnsupportedAppUsage @@ -8236,6 +8239,16 @@ public final class Settings { private static final Validator NOTIFICATION_BADGING_VALIDATOR = BOOLEAN_VALIDATOR; /** + * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right + * swipe). + * + * @hide + */ + public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl"; + + private static final Validator NOTIFICATION_DISMISS_RTL_VALIDATOR = BOOLEAN_VALIDATOR; + + /** * Comma separated list of QS tiles that have been auto-added already. * @hide */ @@ -8537,6 +8550,7 @@ public final class Settings { ASSIST_GESTURE_WAKE_ENABLED, VR_DISPLAY_MODE, NOTIFICATION_BADGING, + NOTIFICATION_DISMISS_RTL, QS_AUTO_ADDED_TILES, SCREENSAVER_ENABLED, SCREENSAVER_COMPONENTS, @@ -8699,6 +8713,7 @@ public final class Settings { VALIDATORS.put(ASSIST_GESTURE_WAKE_ENABLED, ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR); VALIDATORS.put(VR_DISPLAY_MODE, VR_DISPLAY_MODE_VALIDATOR); VALIDATORS.put(NOTIFICATION_BADGING, NOTIFICATION_BADGING_VALIDATOR); + VALIDATORS.put(NOTIFICATION_DISMISS_RTL, NOTIFICATION_DISMISS_RTL_VALIDATOR); VALIDATORS.put(QS_AUTO_ADDED_TILES, QS_AUTO_ADDED_TILES_VALIDATOR); VALIDATORS.put(SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_VALIDATOR); VALIDATORS.put(SCREENSAVER_COMPONENTS, SCREENSAVER_COMPONENTS_VALIDATOR); @@ -12138,6 +12153,13 @@ public final class Settings { "angle_gl_driver_selection_values"; /** + * List of package names that should check ANGLE rules + * @hide + */ + public static final String GLOBAL_SETTINGS_ANGLE_WHITELIST = + "angle_whitelist"; + + /** * Game Update Package global preference for all Apps. * 0 = Default * 1 = All Apps use Game Update Package @@ -13008,48 +13030,37 @@ public final class Settings { "sms_access_restriction_enabled"; /** - * If set to 1, an app must have the READ_PRIVILEGED_PHONE_STATE permission (or be a device - * / profile owner with the READ_PHONE_STATE permission) to access device identifiers. - * - * STOPSHIP: Remove this once we ship with the new device identifier check enabled. - * - * @hide - */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED = - "privileged_device_identifier_check_enabled"; - - /** - * If set to 1, an app that is targeting Q and does not meet the new requirements to access - * device identifiers will receive a SecurityException. + * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE + * permission check for 3P apps. * * STOPSHIP: Remove this once we ship with the new device identifier check enabled. * * @hide */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED = - "privileged_device_identifier_target_q_behavior_enabled"; + public static final String PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED = + "privileged_device_identifier_3p_check_relaxed"; /** * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE - * permission check for 3P apps. + * permission check for preloaded non-privileged apps. * * STOPSHIP: Remove this once we ship with the new device identifier check enabled. * * @hide */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED = - "privileged_device_identifier_3p_check_relaxed"; + public static final String PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED = + "privileged_device_identifier_non_priv_check_relaxed"; /** * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE - * permission check for preloaded non-privileged apps. + * permission check for preloaded privileged apps. * * STOPSHIP: Remove this once we ship with the new device identifier check enabled. * * @hide */ - public static final String PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED = - "privileged_device_identifier_non_priv_check_relaxed"; + public static final String PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED = + "privileged_device_identifier_priv_check_relaxed"; /** * If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored @@ -14592,6 +14603,17 @@ public final class Settings { "android.settings.panel.action.INTERNET_CONNECTIVITY"; /** + * Activity Action: Show a settings dialog containing NFC-related settings. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NFC = + "android.settings.panel.action.NFC"; + + /** * Activity Action: Show a settings dialog containing all volume streams. * <p> * Input: Nothing. diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 0edcb3d8eb6a..4052ed782671 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -40,6 +40,7 @@ public class FeatureFlagUtils { public static final String SAFETY_HUB = "settings_safety_hub"; public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled"; + public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled"; private static final Map<String, String> DEFAULT_FLAGS; private static final Set<String> OBSERVABLE_FLAGS; @@ -53,13 +54,14 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_seamless_transfer", "false"); DEFAULT_FLAGS.put("settings_slice_injection", "false"); DEFAULT_FLAGS.put("settings_systemui_theme", "true"); - DEFAULT_FLAGS.put("settings_wifi_dpp", "false"); - DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false"); - DEFAULT_FLAGS.put("settings_wifi_sharing", "false"); + DEFAULT_FLAGS.put("settings_wifi_dpp", "true"); + DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "true"); + DEFAULT_FLAGS.put("settings_wifi_sharing", "true"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); DEFAULT_FLAGS.put(SAFETY_HUB, "false"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false"); + DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "false"); OBSERVABLE_FLAGS = new HashSet<>(); OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED); diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index dd6231d9c5b0..2142c36f8803 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,17 +16,22 @@ package android.view; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; +import android.util.Property; import android.util.SparseArray; +import android.view.InsetsState.InternalInsetType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetType; -import android.view.InsetsState.InternalInsetType; import com.android.internal.annotations.VisibleForTesting; @@ -39,6 +44,41 @@ import java.util.ArrayList; */ public class InsetsController implements WindowInsetsController { + // TODO: Use animation scaling and more optimal duration. + private static final int ANIMATION_DURATION_MS = 400; + private static final int DIRECTION_NONE = 0; + private static final int DIRECTION_SHOW = 1; + private static final int DIRECTION_HIDE = 2; + @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE}) + private @interface AnimationDirection{} + + /** + * Translation animation evaluator. + */ + private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of( + 0, + (int) (startValue.top + fraction * (endValue.top - startValue.top)), + 0, + (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); + + /** + * Linear animation property + */ + private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> { + InsetsProperty() { + super(Insets.class, "Insets"); + } + + @Override + public Insets get(WindowInsetsAnimationController object) { + return object.getCurrentInsets(); + } + @Override + public void set(WindowInsetsAnimationController object, Insets value) { + object.changeInsets(value); + } + } + private final String TAG = "InsetsControllerImpl"; private final InsetsState mState = new InsetsState(); @@ -58,6 +98,8 @@ public class InsetsController implements WindowInsetsController { private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); + private ObjectAnimator mAnimator; + private @AnimationDirection int mAnimationDirection; public InsetsController(ViewRootImpl viewRoot) { mViewRoot = viewRoot; @@ -122,7 +164,10 @@ public class InsetsController implements WindowInsetsController { public void onControlsChanged(InsetsSourceControl[] activeControls) { if (activeControls != null) { for (InsetsSourceControl activeControl : activeControls) { - mTmpControlArray.put(activeControl.getType(), activeControl); + if (activeControl != null) { + // TODO(b/122982984): Figure out why it can be null. + mTmpControlArray.put(activeControl.getType(), activeControl); + } } } @@ -146,18 +191,40 @@ public class InsetsController implements WindowInsetsController { @Override public void show(@InsetType int types) { + int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - getSourceConsumer(internalTypes.valueAt(i)).show(); + InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (mAnimationDirection == DIRECTION_HIDE) { + // Only one animator (with multiple InsetType) can run at a time. + // previous one should be cancelled for simplicity. + cancelExistingAnimation(); + } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) { + // no-op: already shown or animating in. + // TODO: When we have more than one types: handle specific case when + // show animation is going on, but the current type is not becoming visible. + continue; + } + typesReady |= InsetsState.toPublicType(consumer.getType()); } + applyAnimation(typesReady, true /* show */); } @Override public void hide(@InsetType int types) { + int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - getSourceConsumer(internalTypes.valueAt(i)).hide(); + InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (mAnimationDirection == DIRECTION_SHOW) { + cancelExistingAnimation(); + } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) { + // no-op: already hidden or animating out. + continue; + } + typesReady |= InsetsState.toPublicType(consumer.getType()); } + applyAnimation(typesReady, false /* show */); } @Override @@ -226,6 +293,79 @@ public class InsetsController implements WindowInsetsController { } } + private void applyAnimation(@InsetType final int types, boolean show) { + if (types == 0) { + // nothing to animate. + return; + } + WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { + @Override + public void onReady(WindowInsetsAnimationController controller, int types) { + mAnimator = ObjectAnimator.ofObject( + controller, + new InsetsProperty(), + sEvaluator, + show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(), + show ? controller.getShownStateInsets() : controller.getHiddenStateInsets() + ); + mAnimator.setDuration(ANIMATION_DURATION_MS); + mAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + onAnimationFinish(); + } + + @Override + public void onAnimationEnd(Animator animation) { + onAnimationFinish(); + } + }); + mAnimator.start(); + } + + @Override + public void onCancelled() {} + + private void onAnimationFinish() { + mAnimationDirection = DIRECTION_NONE; + if (show) { + showOnAnimationEnd(types); + } else { + hideOnAnimationEnd(types); + } + } + }; + // TODO: Instead of clearing this here, properly wire up + // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls. + mAnimationControls.clear(); + controlWindowInsetsAnimation(types, listener); + } + + private void hideOnAnimationEnd(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).hide(); + } + } + + private void showOnAnimationEnd(@InsetType int types) { + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + getSourceConsumer(internalTypes.valueAt(i)).show(); + } + } + + /** + * Cancel on-going animation to show/hide {@link InsetType}. + */ + @VisibleForTesting + public void cancelExistingAnimation() { + mAnimationDirection = DIRECTION_NONE; + if (mAnimator != null) { + mAnimator.cancel(); + } + } + void dump(String prefix, PrintWriter pw) { pw.println(prefix); pw.println("InsetsController:"); mState.dump(prefix + " ", pw); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 145b09763676..7937cb69b80e 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,8 +17,8 @@ package android.view; import android.annotation.Nullable; -import android.view.SurfaceControl.Transaction; import android.view.InsetsState.InternalInsetType; +import android.view.SurfaceControl.Transaction; import com.android.internal.annotations.VisibleForTesting; @@ -89,6 +89,11 @@ public class InsetsSourceConsumer { return true; } + @VisibleForTesting + public boolean isVisible() { + return mVisible; + } + private void setVisible(boolean visible) { if (mVisible == visible) { return; diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 529776e542ee..a6af1a296faf 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,7 +16,10 @@ package android.view; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.indexOf; import android.annotation.IntDef; @@ -124,9 +127,10 @@ public class InsetsState implements Parcelable { @Nullable @InsetSide SparseIntArray typeSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; + boolean[] typeVisibilityMap = new boolean[SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); - if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME + if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL && legacyContentInsets != null && legacyStableInsets != null) { WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets); WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets); @@ -136,22 +140,29 @@ public class InsetsState implements Parcelable { if (source == null) { continue; } + if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL + && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) { + typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible(); + continue; + } + processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, - typeSideMap); + typeSideMap, typeVisibilityMap); // IME won't be reported in max insets as the size depends on the EditorInfo of the IME // target. if (source.getType() != TYPE_IME) { processSource(source, relativeFrameMax, true /* ignoreVisibility */, - typeMaxInsetsMap, null /* typeSideMap */); + typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */); } } - return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, isScreenRound, + return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, alwaysConsumeNavBar, cutout); } private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, - Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap) { + Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap, + @Nullable boolean[] typeVisibilityMap) { Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility); int index = indexOf(toPublicType(source.getType())); @@ -162,6 +173,10 @@ public class InsetsState implements Parcelable { typeInsetsMap[index] = Insets.max(existing, insets); } + if (typeVisibilityMap != null) { + typeVisibilityMap[index] = source.isVisible(); + } + if (typeSideMap != null && !Insets.NONE.equals(insets)) { @InsetSide int insetSide = getInsetSide(insets); if (insetSide != INSET_SIDE_UNKNWON) { diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java index 3c9ce788b706..6f5a85d210af 100644 --- a/core/java/android/view/RemoteAnimationAdapter.java +++ b/core/java/android/view/RemoteAnimationAdapter.java @@ -18,7 +18,6 @@ package android.view; import android.annotation.UnsupportedAppUsage; import android.app.ActivityOptions; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -52,6 +51,7 @@ public class RemoteAnimationAdapter implements Parcelable { private final IRemoteAnimationRunner mRunner; private final long mDuration; private final long mStatusBarTransitionDelay; + private final boolean mChangeNeedsSnapshot; /** @see #getCallingPid */ private int mCallingPid; @@ -59,21 +59,31 @@ public class RemoteAnimationAdapter implements Parcelable { /** * @param runner The interface that gets notified when we actually need to start the animation. * @param duration The duration of the animation. + * @param changeNeedsSnapshot For change transitions, whether this should create a snapshot by + * screenshotting the task. * @param statusBarTransitionDelay The desired delay for all visual animations in the * status bar caused by this app animation in millis. */ @UnsupportedAppUsage public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, - long statusBarTransitionDelay) { + long statusBarTransitionDelay, boolean changeNeedsSnapshot) { mRunner = runner; mDuration = duration; + mChangeNeedsSnapshot = changeNeedsSnapshot; mStatusBarTransitionDelay = statusBarTransitionDelay; } + @UnsupportedAppUsage + public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, + long statusBarTransitionDelay) { + this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */); + } + public RemoteAnimationAdapter(Parcel in) { mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder()); mDuration = in.readLong(); mStatusBarTransitionDelay = in.readLong(); + mChangeNeedsSnapshot = in.readBoolean(); } public IRemoteAnimationRunner getRunner() { @@ -88,6 +98,10 @@ public class RemoteAnimationAdapter implements Parcelable { return mStatusBarTransitionDelay; } + public boolean getChangeNeedsSnapshot() { + return mChangeNeedsSnapshot; + } + /** * To be called by system_server to keep track which pid is running this animation. */ @@ -112,6 +126,7 @@ public class RemoteAnimationAdapter implements Parcelable { dest.writeStrongInterface(mRunner); dest.writeLong(mDuration); dest.writeLong(mStatusBarTransitionDelay); + dest.writeBoolean(mChangeNeedsSnapshot); } public static final Creator<RemoteAnimationAdapter> CREATOR diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 483280e36bd1..cd3decf4e981 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3985,6 +3985,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int SCROLL_AXIS_VERTICAL = 1 << 1; /** + * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain the default + * long press action will be inhibited. However, to account for the possibility of incorrect + * classification, the default long press timeout will instead be increased for some situations + * by the following factor. + * Likewise, the touch slop for allowing long press will be increased when gesture is uncertain. + */ + private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2; + + /** * Controls the over-scroll mode for this view. * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)}, * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}, @@ -9058,35 +9067,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid=" + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + ", visible=" + (getVisibility() == VISIBLE) - + ": alreadyNotifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)); + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - final ViewStructure structure = session.newViewStructure(this); - onProvideContentCaptureStructure(structure, /* flags= */ 0); - session.notifyViewAppeared(structure); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; + + // The code below doesn't take much for a unique view, but it's called for all views + // the first time the view hiearchy is laid off, which could acccumulative delay the + // initial layout. Hence, we're postponing it to a later stage - it might still cost a + // lost frame (or more), but that jank cost would only happen after the 1st layout. + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { + final ViewStructure structure = session.newViewStructure(this); + onProvideContentCaptureStructure(structure, /* flags= */ 0); + session.notifyViewAppeared(structure); + }, /* token= */ null); } else { if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0 || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) { if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) { - Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this - + ": notifiedAppeared=" - + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid=" + + isLaidOut() + ", visibleToUser=" + isVisibleToUser() + + ", visible=" + (getVisibility() == VISIBLE) + + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4 + & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0)); } return; } - // All good: notify it... - session.notifyViewDisappeared(getAutofillId()); - // ...and set the flags mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED; mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED; + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, + () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null); } } @@ -9154,7 +9171,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ContentCaptureSession session = null; if (mParent instanceof View) { - session = ((View) mParent).getContentCaptureSession(); + session = ((View) mParent).getContentCaptureSession(ccm); } return session != null ? session : ccm.getMainContentCaptureSession(); @@ -14780,8 +14797,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, drawableHotspotChanged(x, y); } + final int motionClassification = event.getClassification(); + final boolean ambiguousGesture = + motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; + int touchSlop = mTouchSlop; + if (ambiguousGesture && hasPendingLongPressCallback()) { + if (!pointInView(x, y, touchSlop)) { + // The default action here is to cancel long press. But instead, we + // just extend the timeout here, in case the classification + // stays ambiguous. + removeLongPressCallback(); + long delay = ViewConfiguration.getLongPressTimeout() + * AMBIGUOUS_GESTURE_MULTIPLIER; + // Subtract the time already spent + delay -= event.getEventTime() - event.getDownTime(); + checkForLongClick(delay, x, y); + } + touchSlop *= AMBIGUOUS_GESTURE_MULTIPLIER; + } + // Be lenient about moving outside of buttons - if (!pointInView(x, y, mTouchSlop)) { + if (!pointInView(x, y, touchSlop)) { // Outside button // Remove any future long press/tap checks removeTapCallback(); @@ -14791,6 +14827,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } + + final boolean deepPress = + motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; + if (deepPress && hasPendingLongPressCallback()) { + // process the long click action immediately + removeLongPressCallback(); + checkForLongClick(0 /* send immediately */, x, y); + } + break; } @@ -14825,6 +14870,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Return true if the long press callback is scheduled to run sometime in the future. + * Return false if there is no scheduled long press callback at the moment. + */ + private boolean hasPendingLongPressCallback() { + if (mPendingCheckForLongPress == null) { + return false; + } + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo == null) { + return false; + } + return attachInfo.mHandler.hasCallbacks(mPendingCheckForLongPress); + } + + /** * Remove the pending click action */ @UnsupportedAppUsage diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index e8088303eac7..c1536ae2b4ae 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -66,6 +66,7 @@ public final class WindowInsets { private final Insets[] mTypeInsetsMap; private final Insets[] mTypeMaxInsetsMap; + private final boolean[] mTypeVisibilityMap; @Nullable private Rect mTempRect; private final boolean mIsRound; @@ -106,6 +107,7 @@ public final class WindowInsets { public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) { this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect), + createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)), isRound, alwaysConsumeNavBar, displayCutout); } @@ -122,7 +124,9 @@ public final class WindowInsets { * @hide */ public WindowInsets(@Nullable Insets[] typeInsetsMap, - @Nullable Insets[] typeMaxInsetsMap, boolean isRound, + @Nullable Insets[] typeMaxInsetsMap, + boolean[] typeVisibilityMap, + boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed @@ -134,6 +138,7 @@ public final class WindowInsets { ? new Insets[SIZE] : typeMaxInsetsMap.clone(); + mTypeVisibilityMap = typeVisibilityMap; mIsRound = isRound; mAlwaysConsumeNavBar = alwaysConsumeNavBar; @@ -148,8 +153,8 @@ public final class WindowInsets { * @param src Source to copy insets from */ public WindowInsets(WindowInsets src) { - this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mIsRound, src.mAlwaysConsumeNavBar, - displayCutoutCopyConstructorArgument(src)); + this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, + src.mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(src)); } private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) { @@ -200,7 +205,7 @@ public final class WindowInsets { /** @hide */ @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { - this(createCompatTypeMap(systemWindowInsets), null, false, false, null); + this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null); } /** @@ -225,6 +230,20 @@ public final class WindowInsets { typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom); } + private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetMap) { + boolean[] typeVisibilityMap = new boolean[SIZE]; + if (typeInsetMap == null) { + return typeVisibilityMap; + } + for (int i = FIRST; i <= LAST; i = i << 1) { + int index = indexOf(i); + if (!Insets.NONE.equals(typeInsetMap[index])) { + typeVisibilityMap[index] = true; + } + } + return typeVisibilityMap; + } + /** * Used to provide a safe copy of the system window insets to pass through * to the existing fitSystemWindows method and other similar internals. @@ -297,6 +316,27 @@ public final class WindowInsets { } /** + * Returns whether a set of windows that may cause insets is currently visible on screen, + * regardless of whether it actually overlaps with this window. + * + * @param typeMask Bit mask of {@link InsetType}s to query visibility status. + * @return {@code true} if and only if all windows included in {@code typeMask} are currently + * visible on screen. + * @hide pending unhide + */ + public boolean isVisible(@InsetType int typeMask) { + for (int i = FIRST; i <= LAST; i = i << 1) { + if ((typeMask & i) == 0) { + continue; + } + if (!mTypeVisibilityMap[indexOf(i)]) { + return false; + } + } + return true; + } + + /** * Returns the left system window inset in pixels. * * <p>The system window inset represents the area of a full-screen window that is @@ -392,6 +432,7 @@ public final class WindowInsets { public WindowInsets consumeDisplayCutout() { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, null /* displayCutout */); } @@ -437,6 +478,7 @@ public final class WindowInsets { @NonNull public WindowInsets consumeSystemWindowInsets() { return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(this)); } @@ -594,7 +636,7 @@ public final class WindowInsets { @NonNull public WindowInsets consumeStableInsets() { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null, - mIsRound, mAlwaysConsumeNavBar, + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(this)); } @@ -671,6 +713,7 @@ public final class WindowInsets { mStableInsetsConsumed ? null : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom), + mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar, mDisplayCutoutConsumed ? null @@ -692,14 +735,15 @@ public final class WindowInsets { && mDisplayCutoutConsumed == that.mDisplayCutoutConsumed && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap) && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap) + && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap) && Objects.equals(mDisplayCutout, that.mDisplayCutout); } @Override public int hashCode() { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), - mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed, - mStableInsetsConsumed, mDisplayCutoutConsumed); + Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, + mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed); } @@ -754,6 +798,7 @@ public final class WindowInsets { private final Insets[] mTypeInsetsMap; private final Insets[] mTypeMaxInsetsMap; + private final boolean[] mTypeVisibilityMap; private boolean mSystemInsetsConsumed = true; private boolean mStableInsetsConsumed = true; @@ -768,6 +813,7 @@ public final class WindowInsets { public Builder() { mTypeInsetsMap = new Insets[SIZE]; mTypeMaxInsetsMap = new Insets[SIZE]; + mTypeVisibilityMap = new boolean[SIZE]; } /** @@ -778,6 +824,7 @@ public final class WindowInsets { public Builder(WindowInsets insets) { mTypeInsetsMap = insets.mTypeInsetsMap.clone(); mTypeMaxInsetsMap = insets.mTypeMaxInsetsMap.clone(); + mTypeVisibilityMap = insets.mTypeVisibilityMap.clone(); mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed; mStableInsetsConsumed = insets.mStableInsetsConsumed; mDisplayCutout = displayCutoutCopyConstructorArgument(insets); @@ -862,6 +909,29 @@ public final class WindowInsets { } /** + * Sets whether windows that can cause insets are currently visible on screen. + * + * + * @see #isVisible(int) + * + * @param typeMask The bitmask of {@link InsetType} to set the visibility for. + * @param visible Whether to mark the windows as visible or not. + * + * @return itself + * @hide pending unhide + */ + @NonNull + public Builder setVisible(@InsetType int typeMask, boolean visible) { + for (int i = FIRST; i <= LAST; i = i << 1) { + if ((typeMask & i) == 0) { + continue; + } + mTypeVisibilityMap[indexOf(i)] = visible; + } + return this; + } + + /** * Sets the stable insets in pixels. * * <p>The stable inset represents the area of a full-screen window that <b>may</b> be @@ -916,8 +986,8 @@ public final class WindowInsets { @NonNull public WindowInsets build() { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, - mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mIsRound, - mAlwaysConsumeNavBar, mDisplayCutout); + mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, + mIsRound, mAlwaysConsumeNavBar, mDisplayCutout); } } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index a35be273f3bf..b70832315e2c 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.WindowInsets.Type.ime; + import android.annotation.NonNull; import android.view.WindowInsets.Type.InsetType; @@ -32,11 +34,11 @@ public interface WindowInsetsController { * <p> * Note that if the window currently doesn't have control over a certain type, it will apply the * change as soon as the window gains control. The app can listen to the event by observing - * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in - * {@link WindowInsets}. + * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}. * * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app * would like to make appear on screen. + * @hide */ void show(@InsetType int types); @@ -45,11 +47,11 @@ public interface WindowInsetsController { * <p> * Note that if the window currently doesn't have control over a certain type, it will apply the * change as soon as the window gains control. The app can listen to the event by observing - * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in - * {@link WindowInsets}. + * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}. * * @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app * would like to make disappear. + * @hide */ void hide(@InsetType int types); @@ -60,7 +62,50 @@ public interface WindowInsetsController { * @param types The {@link InsetType}s the application has requested to control. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * windows are ready to be controlled, among other callbacks. + * @hide */ void controlWindowInsetsAnimation(@InsetType int types, @NonNull WindowInsetsAnimationControlListener listener); + + /** + * Lets the application control the animation for showing the IME in a frame-by-frame manner by + * modifying the position of the IME when it's causing insets. + * + * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the + * IME are ready to be controlled, among other callbacks. + */ + default void controlInputMethodAnimation( + @NonNull WindowInsetsAnimationControlListener listener) { + controlWindowInsetsAnimation(ime(), listener); + } + + /** + * Makes the IME appear on screen. + * <p> + * Note that if the window currently doesn't have control over the IME, because it doesn't have + * focus, it will apply the change as soon as the window gains control. The app can listen to + * the event by observing {@link View#onApplyWindowInsets} and checking visibility with + * {@link WindowInsets#isVisible}. + * + * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener) + * @see #hideInputMethod() + */ + default void showInputMethod() { + show(ime()); + } + + /** + * Makes the IME disappear on screen. + * <p> + * Note that if the window currently doesn't have control over IME, because it doesn't have + * focus, it will apply the change as soon as the window gains control. The app can listen to + * the event by observing {@link View#onApplyWindowInsets} and checking visibility with + * {@link WindowInsets#isVisible}. + * + * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener) + * @see #showInputMethod() + */ + default void hideInputMethod() { + hide(ime()); + } } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 6ed2d801110c..c425e7bd3700 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -34,8 +34,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import dalvik.system.CloseGuard; - import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -148,9 +146,6 @@ public abstract class ContentCaptureSession implements AutoCloseable { @Retention(RetentionPolicy.SOURCE) @interface FlushReason{} - - private final CloseGuard mCloseGuard = CloseGuard.get(); - private final Object mLock = new Object(); /** @@ -185,7 +180,6 @@ public abstract class ContentCaptureSession implements AutoCloseable { @VisibleForTesting public ContentCaptureSession(@NonNull String id) { mId = Preconditions.checkNotNull(id); - mCloseGuard.open("destroy"); } /** @hide */ @@ -251,8 +245,6 @@ public abstract class ContentCaptureSession implements AutoCloseable { } mDestroyed = true; - mCloseGuard.close(); - // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote // id) and send it to the cache of batched commands if (VERBOSE) { @@ -288,18 +280,6 @@ public abstract class ContentCaptureSession implements AutoCloseable { destroy(); } - @Override - protected void finalize() throws Throwable { - try { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - destroy(); - } finally { - super.finalize(); - } - } - /** * Notifies the Content Capture Service that a node has been added to the view structure. * diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index d09323d3f8ad..112653aa34e3 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -219,7 +219,7 @@ public interface InputMethod { @MainThread default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + @NonNull IBinder startInputToken, boolean shouldPreRenderIme) { if (restarting) { restartInput(inputConnection, editorInfo); } else { diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index 77cb4cd28763..4d917a1b1968 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -103,10 +103,9 @@ public final class ActionsSuggestionsHelper { final String modelName = String.format( Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion); final int hash = Objects.hash( - messages.stream() - .map(ConversationActions.Message::getText) - .collect(Collectors.toList()), - context.getPackageName()); + messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage), + context.getPackageName(), + System.currentTimeMillis()); return SelectionSessionLogger.SignatureParser.createSignature( SelectionSessionLogger.CLASSIFIER_ID, modelName, hash); } @@ -116,7 +115,7 @@ public final class ActionsSuggestionsHelper { private int mNextUserId = FIRST_NON_LOCAL_USER; private int encode(Person person) { - if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) { + if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) { return USER_LOCAL; } Integer result = mMapping.get(person); @@ -128,4 +127,8 @@ public final class ActionsSuggestionsHelper { return result; } } + + private static int hashMessage(ConversationActions.Message message) { + return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime()); + } } diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index f7c1a2640dc5..502181f633b6 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -109,9 +109,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_LOCAL = + public static final Person PERSON_USER_SELF = new Person.Builder() - .setKey("text-classifier-conversation-actions-local-user") + .setKey("text-classifier-conversation-actions-user-self") .build(); /** @@ -123,9 +123,9 @@ public final class ConversationActions implements Parcelable { * * @see Builder#Builder(Person) */ - public static final Person PERSON_USER_REMOTE = + public static final Person PERSON_USER_OTHERS = new Person.Builder() - .setKey("text-classifier-conversation-actions-remote-user") + .setKey("text-classifier-conversation-actions-user-others") .build(); @Nullable @@ -235,10 +235,10 @@ public final class ConversationActions implements Parcelable { /** * Constructs a builder. * - * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} + * @param author the person that composed the message, use {@link #PERSON_USER_SELF} * to represent the local user. If it is not possible to identify the * remote user that the local user is conversing with, use - * {@link #PERSON_USER_REMOTE} to represent a remote user. + * {@link #PERSON_USER_OTHERS} to represent a remote user. */ public Builder(@NonNull Person author) { mAuthor = Preconditions.checkNotNull(author); diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index ed862064be67..10c7adef28fd 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -73,9 +73,16 @@ public final class TextClassificationManager { /** * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}. * If this is null, this method returns a default text classifier (i.e. either the system text - * classifier if one exists, or a local text classifier running in this app.) + * classifier if one exists, or a local text classifier running in this process.) + * <p> + * Note that if system textclassifier is in use, requests will be sent to a textclassifier + * package provided from OEM. If you want to make sure the requests are handled in your own + * process, you should consider {@link #getLocalTextClassifier()} instead. However, the local + * textclassifier may return inferior results to those returned by the system + * textclassifier. * * @see #setTextClassifier(TextClassifier) + * @see #getLocalTextClassifier() */ @NonNull public TextClassifier getTextClassifier() { @@ -215,7 +222,13 @@ public final class TextClassificationManager { return TextClassifier.NO_OP; } - private TextClassifier getLocalTextClassifier() { + /** + * Returns a local textclassifier, which is running in this process. + * + * @see #getTextClassifier() + */ + @NonNull + public TextClassifier getLocalTextClassifier() { synchronized (mLock) { if (mLocalTextClassifier == null) { if (getSettings().isLocalTextClassifierEnabled()) { diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java index b84f6f07e414..cd13cc0ec577 100644 --- a/core/java/android/view/textclassifier/TextClassifierEvent.java +++ b/core/java/android/view/textclassifier/TextClassifierEvent.java @@ -72,7 +72,7 @@ public final class TextClassifierEvent implements Parcelable { TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION, TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION, TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL, - TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY}) + TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED}) public @interface Type { // For custom event types, use range 1,000,000+. } @@ -121,7 +121,7 @@ public final class TextClassifierEvent implements Parcelable { @Category private final int mEventCategory; @Type private final int mEventType; - @Nullable private final String mEntityType; + @Nullable private final String[] mEntityTypes; @Nullable private final TextClassificationContext mEventContext; @Nullable private final String mResultId; private final int mEventIndex; @@ -139,11 +139,12 @@ public final class TextClassifierEvent implements Parcelable { // Language detection. @Nullable private final String mLanguage; + private final float mScore; private TextClassifierEvent( int eventCategory, int eventType, - String entityType, + String[] entityTypes, TextClassificationContext eventContext, String resultId, int eventIndex, @@ -154,10 +155,11 @@ public final class TextClassifierEvent implements Parcelable { int relativeSuggestedWordStartIndex, int relativeSuggestedWordEndIndex, int[] actionIndex, - String language) { + String language, + float score) { mEventCategory = eventCategory; mEventType = eventType; - mEntityType = entityType; + mEntityTypes = entityTypes; mEventContext = eventContext; mResultId = resultId; mEventIndex = eventIndex; @@ -169,6 +171,7 @@ public final class TextClassifierEvent implements Parcelable { mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex; mActionIndices = actionIndex; mLanguage = language; + mScore = score; } @Override @@ -180,7 +183,7 @@ public final class TextClassifierEvent implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mEventCategory); dest.writeInt(mEventType); - dest.writeString(mEntityType); + dest.writeStringArray(mEntityTypes); dest.writeParcelable(mEventContext, flags); dest.writeString(mResultId); dest.writeInt(mEventIndex); @@ -192,13 +195,14 @@ public final class TextClassifierEvent implements Parcelable { dest.writeInt(mRelativeSuggestedWordEndIndex); dest.writeIntArray(mActionIndices); dest.writeString(mLanguage); + dest.writeFloat(mScore); } private static TextClassifierEvent readFromParcel(Parcel in) { return new TextClassifierEvent( /* eventCategory= */ in.readInt(), /* eventType= */ in.readInt(), - /* entityType= */ in.readString(), + /* entityTypes=*/ in.readStringArray(), /* eventContext= */ in.readParcelable(null), /* resultId= */ in.readString(), /* eventIndex= */ in.readInt(), @@ -209,7 +213,8 @@ public final class TextClassifierEvent implements Parcelable { /* relativeSuggestedWordStartIndex= */ in.readInt(), /* relativeSuggestedWordEndIndex= */ in.readInt(), /* actionIndices= */ in.createIntArray(), - /* language= */ in.readString()); + /* language= */ in.readString(), + /* score= */ in.readFloat()); } /** @@ -229,11 +234,11 @@ public final class TextClassifierEvent implements Parcelable { } /** - * Returns the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}. + * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}. */ - @Nullable - public String getEntityType() { - return mEntityType; + @NonNull + public String[] getEntityTypes() { + return mEntityTypes; } /** @@ -327,13 +332,20 @@ public final class TextClassifierEvent implements Parcelable { } /** + * Returns the score of the suggestion. + */ + public float getScore() { + return mScore; + } + + /** * Builder to build a text classifier event. */ public static final class Builder { private final int mEventCategory; private final int mEventType; - @Nullable private String mEntityType; + private String[] mEntityTypes = new String[0]; @Nullable private TextClassificationContext mEventContext; @Nullable private String mResultId; private int mEventIndex; @@ -345,6 +357,7 @@ public final class TextClassifierEvent implements Parcelable { private int mRelativeSuggestedWordEndIndex; private int[] mActionIndices = new int[0]; @Nullable private String mLanguage; + private float mScore; /** * Creates a builder for building {@link TextClassifierEvent}s. @@ -358,11 +371,12 @@ public final class TextClassifierEvent implements Parcelable { } /** - * Sets the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}. + * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}. */ @NonNull - public Builder setEntityType(@Nullable String entityType) { - mEntityType = entityType; + public Builder setEntityTypes(@NonNull String... entityTypes) { + mEntityTypes = new String[entityTypes.length]; + System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length); return this; } @@ -478,6 +492,15 @@ public final class TextClassifierEvent implements Parcelable { } /** + * Sets the score of the suggestion. + */ + @NonNull + public Builder setScore(float score) { + mScore = score; + return this; + } + + /** * Builds and returns a text classifier event. */ @NonNull @@ -486,7 +509,7 @@ public final class TextClassifierEvent implements Parcelable { return new TextClassifierEvent( mEventCategory, mEventType, - mEntityType, + mEntityTypes, mEventContext, mResultId, mEventIndex, @@ -497,7 +520,8 @@ public final class TextClassifierEvent implements Parcelable { mRelativeSuggestedWordStartIndex, mRelativeSuggestedWordEndIndex, mActionIndices, - mLanguage); + mLanguage, + mScore); } // TODO: Add build(boolean validate). } @@ -507,7 +531,7 @@ public final class TextClassifierEvent implements Parcelable { StringBuilder out = new StringBuilder(128); out.append("TextClassifierEvent{"); out.append("mEventCategory=").append(mEventCategory); - out.append(", mEventType=").append(mEventType); + out.append(", mEventTypes=").append(Arrays.toString(mEntityTypes)); out.append(", mEventContext=").append(mEventContext); out.append(", mResultId=").append(mResultId); out.append(", mEventIndex=").append(mEventIndex); @@ -519,6 +543,7 @@ public final class TextClassifierEvent implements Parcelable { out.append(", mRelativeSuggestedWordEndIndex=").append(mRelativeSuggestedWordEndIndex); out.append(", mActionIndices=").append(Arrays.toString(mActionIndices)); out.append(", mLanguage=").append(mLanguage); + out.append(", mScore=").append(mScore); out.append("}"); return out.toString(); } diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java index 439e594cc8fe..5563dfc2eee5 100644 --- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java +++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java @@ -15,12 +15,15 @@ */ package android.view.textclassifier; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_SESSION_ID; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SESSION_ID; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_VERSION; import android.metrics.LogMaker; @@ -60,16 +63,30 @@ public final class TextClassifierEventTronLogger { return; } final LogMaker log = new LogMaker(category) - .setType(getLogType(event)) - .addTaggedData(FIELD_SELECTION_SESSION_ID, event.getResultId()) + .setSubtype(getLogType(event)) + .addTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID, event.getResultId()) .addTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME, event.getEventTime()) .addTaggedData(FIELD_TEXTCLASSIFIER_MODEL, SelectionSessionLogger.SignatureParser.getModelName(event.getResultId())) - .addTaggedData(FIELD_SELECTION_ENTITY_TYPE, event.getEntityType()); + .addTaggedData(FIELD_TEXT_CLASSIFIER_SCORE, event.getScore()); + + String[] entityTypes = event.getEntityTypes(); + // TRON does not support a field of list type, and thus workaround by store them + // in three separate fields. This is no longer an issue once we have moved to Westworld. + if (entityTypes.length >= 1) { + log.addTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE, entityTypes[0]); + } + if (entityTypes.length >= 2) { + log.addTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE, entityTypes[1]); + } + if (entityTypes.length >= 3) { + log.addTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE, entityTypes[2]); + } TextClassificationContext eventContext = event.getEventContext(); if (eventContext != null) { - log.addTaggedData(FIELD_SELECTION_WIDGET_TYPE, eventContext.getWidgetType()); - log.addTaggedData(FIELD_SELECTION_WIDGET_VERSION, eventContext.getWidgetVersion()); + log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE, eventContext.getWidgetType()); + log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION, + eventContext.getWidgetVersion()); log.setPackageName(eventContext.getPackageName()); } mMetricsLogger.write(log); @@ -94,6 +111,8 @@ public final class TextClassifierEventTronLogger { return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN; case TextClassifierEvent.TYPE_MANUAL_REPLY: return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY; + case TextClassifierEvent.TYPE_ACTIONS_GENERATED: + return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED; default: return MetricsEvent.VIEW_UNKNOWN; } @@ -127,14 +146,22 @@ public final class TextClassifierEventTronLogger { if (!Log.ENABLE_FULL_LOGGING) { return; } - final String id = String.valueOf(log.getTaggedData(FIELD_SELECTION_SESSION_ID)); + final String id = String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID)); final String categoryName = toCategoryName(log.getCategory()); - final String eventName = toEventName(log.getType()); - final String widgetType = String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_TYPE)); + final String eventName = toEventName(log.getSubtype()); + final String widgetType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE)); final String widgetVersion = - String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_VERSION)); + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION)); final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL)); - final String entityType = String.valueOf(log.getTaggedData(FIELD_SELECTION_ENTITY_TYPE)); + final String firstEntityType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE)); + final String secondEntityType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE)); + final String thirdEntityType = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE)); + final String score = + String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE)); StringBuilder builder = new StringBuilder(); builder.append("writeEvent: "); @@ -144,7 +171,10 @@ public final class TextClassifierEventTronLogger { builder.append(", widgetType=").append(widgetType); builder.append(", widgetVersion=").append(widgetVersion); builder.append(", model=").append(model); - builder.append(", entityType=").append(entityType); + builder.append(", firstEntityType=").append(firstEntityType); + builder.append(", secondEntityType=").append(secondEntityType); + builder.append(", thirdEntityType=").append(thirdEntityType); + builder.append(", score=").append(score); Log.v(TAG, builder.toString()); } diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 3a1c4576ab3e..de1f3df61462 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -150,6 +150,7 @@ public class WebViewZygote { } try { + String abi = sPackage.applicationInfo.primaryCpuAbi; sZygote = Process.ZYGOTE_PROCESS.startChildZygote( "com.android.internal.os.WebViewZygoteInit", "webview_zygote", @@ -158,39 +159,40 @@ public class WebViewZygote { null, // gids 0, // runtimeFlags "webview_zygote", // seInfo - sPackage.applicationInfo.primaryCpuAbi, // abi + abi, // abi TextUtils.join(",", Build.SUPPORTED_ABIS), null, // instructionSet Process.FIRST_ISOLATED_UID, Process.LAST_ISOLATED_UID); - - // All the work below is usually done by LoadedApk, but the zygote can't talk to - // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so - // doesn't have an ActivityThread and can't use Binder. - // Instead, figure out the paths here, in the system server where we have access to - // the package manager. Reuse the logic from LoadedApk to determine the correct - // paths and pass them to the zygote as strings. - final List<String> zipPaths = new ArrayList<>(10); - final List<String> libPaths = new ArrayList<>(10); - LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths); - final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); - final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - - String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo); - - // In the case where the ApplicationInfo has been modified by the stub WebView, - // we need to use the original ApplicationInfo to determine what the original classpath - // would have been to use as a cache key. - LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null); - final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress()); - Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); - sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey, - Build.SUPPORTED_ABIS[0]); + if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) { + // No stub WebView is involved here, so we can preload the package the "clean" way + // using the ApplicationInfo. + sZygote.preloadApp(sPackage.applicationInfo, abi); + } else { + // Legacy path to support the stub WebView. + // Reuse the logic from LoadedApk to determine the correct paths and pass them to + // the zygote as strings. + final List<String> zipPaths = new ArrayList<>(10); + final List<String> libPaths = new ArrayList<>(10); + LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths); + final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); + final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) : + TextUtils.join(File.pathSeparator, zipPaths); + + String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo); + + // Use the original ApplicationInfo to determine what the original classpath would + // have been to use as a cache key. + LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null); + final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) : + TextUtils.join(File.pathSeparator, zipPaths); + + Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); + sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey, + Build.SUPPORTED_ABIS[0]); + } } catch (Exception e) { Log.e(LOGTAG, "Error connecting to webview zygote", e); stopZygoteLocked(); diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java index 7c371cb18878..d0102a72e703 100644 --- a/core/java/com/android/internal/app/AssistUtils.java +++ b/core/java/com/android/internal/app/AssistUtils.java @@ -17,13 +17,10 @@ package com.android.internal.app; import android.annotation.NonNull; -import android.app.SearchManager; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -31,8 +28,6 @@ import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; -import com.android.internal.R; - import java.util.ArrayList; import java.util.Set; @@ -44,14 +39,6 @@ public class AssistUtils { private static final String TAG = "AssistUtils"; - /** - * Sentinel value for "no default assistant specified." - * - * Empty string is already used to represent an explicit setting of No Assistant. null cannot - * be used because we can't represent a null value in XML. - */ - private static final String UNSET = "#+UNSET"; - private final Context mContext; private final IVoiceInteractionManagerService mVoiceInteractionManagerService; @@ -186,37 +173,9 @@ public class AssistUtils { Settings.Secure.ASSISTANT, userId); if (setting != null) { return ComponentName.unflattenFromString(setting); - } - - final String defaultSetting = mContext.getResources().getString( - R.string.config_defaultAssistantComponentName); - if (defaultSetting != null && !defaultSetting.equals(UNSET)) { - return ComponentName.unflattenFromString(defaultSetting); - } - - // Fallback to keep backward compatible behavior when there is no user setting. - if (activeServiceSupportsAssistGesture()) { - return getActiveServiceComponentName(); - } - - if (UNSET.equals(defaultSetting)) { + } else { return null; } - - final SearchManager searchManager = - (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); - if (searchManager == null) { - return null; - } - final Intent intent = searchManager.getAssistIntent(false); - PackageManager pm = mContext.getPackageManager(); - ResolveInfo info = pm.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY, - userId); - if (info != null) { - return new ComponentName(info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - } - return null; } public static boolean isPreinstalledAssistant(Context context, ComponentName assistant) { diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 30137e3893ff..42acb09d50d6 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -513,6 +513,7 @@ public class ChooserActivity extends ResolverActivity { void queryTargetServices(ChooserListAdapter adapter) { final PackageManager pm = getPackageManager(); + ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class); int targetsToQuery = 0; for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) { final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); @@ -522,6 +523,11 @@ public class ChooserActivity extends ResolverActivity { continue; } final ActivityInfo ai = dri.getResolveInfo().activityInfo; + if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS + && sm.hasShareTargets(ai.packageName)) { + // Share targets will be queried from ShortcutManager + continue; + } final Bundle md = ai.metaData; final String serviceName = md != null ? convertServiceName(ai.packageName, md.getString(ChooserTargetService.META_DATA_NAME)) : null; diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java index c093fe512186..2ac0e4de58ac 100644 --- a/core/java/com/android/internal/app/ColorDisplayController.java +++ b/core/java/com/android/internal/app/ColorDisplayController.java @@ -16,28 +16,20 @@ package com.android.internal.app; -import android.annotation.IntDef; import android.annotation.NonNull; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.metrics.LogMaker; +import android.hardware.display.ColorDisplayManager; +import android.hardware.display.ColorDisplayManager.AutoMode; +import android.hardware.display.ColorDisplayManager.ColorMode; import android.net.Uri; import android.os.Handler; import android.os.Looper; -import android.os.SystemProperties; import android.provider.Settings.Secure; -import android.provider.Settings.System; import android.util.Slog; -import com.android.internal.R; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.time.LocalDateTime; import java.time.LocalTime; /** @@ -51,67 +43,12 @@ public final class ColorDisplayController { private static final String TAG = "ColorDisplayController"; private static final boolean DEBUG = false; - @Retention(RetentionPolicy.SOURCE) - @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT }) - public @interface AutoMode {} - - /** - * Auto mode value to prevent Night display from being automatically activated. It can still - * be activated manually via {@link #setActivated(boolean)}. - * - * @see #setAutoMode(int) - */ - public static final int AUTO_MODE_DISABLED = 0; - /** - * Auto mode value to automatically activate Night display at a specific start and end time. - * - * @see #setAutoMode(int) - * @see #setCustomStartTime(LocalTime) - * @see #setCustomEndTime(LocalTime) - */ - public static final int AUTO_MODE_CUSTOM = 1; - /** - * Auto mode value to automatically activate Night display from sunset to sunrise. - * - * @see #setAutoMode(int) - */ - public static final int AUTO_MODE_TWILIGHT = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC }) - public @interface ColorMode {} - - /** - * Color mode with natural colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_NATURAL = 0; - /** - * Color mode with boosted colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_BOOSTED = 1; - /** - * Color mode with saturated colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_SATURATED = 2; - /** - * Color mode with automatic colors. - * - * @see #setColorMode(int) - */ - public static final int COLOR_MODE_AUTOMATIC = 3; - private final Context mContext; private final int mUserId; + private final ColorDisplayManager mColorDisplayManager; private ContentObserver mContentObserver; private Callback mCallback; - private MetricsLogger mMetricsLogger; public ColorDisplayController(@NonNull Context context) { this(context, ActivityManager.getCurrentUser()); @@ -120,14 +57,14 @@ public final class ColorDisplayController { public ColorDisplayController(@NonNull Context context, int userId) { mContext = context.getApplicationContext(); mUserId = userId; + mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class); } /** * Returns {@code true} when Night display is activated (the display is tinted red). */ public boolean isActivated() { - return Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_ACTIVATED, 0, mUserId) == 1; + return mColorDisplayManager.isNightDisplayActivated(); } /** @@ -137,40 +74,16 @@ public final class ColorDisplayController { * @return {@code true} if the activated value was set successfully */ public boolean setActivated(boolean activated) { - if (isActivated() != activated) { - Secure.putStringForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, - LocalDateTime.now().toString(), - mUserId); - } - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_ACTIVATED, activated ? 1 : 0, mUserId); + return mColorDisplayManager.setNightDisplayActivated(activated); } /** * Returns the current auto mode value controlling when Night display will be automatically - * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or - * {@link #AUTO_MODE_TWILIGHT}. + * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}. */ public @AutoMode int getAutoMode() { - int autoMode = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_AUTO_MODE, -1, mUserId); - if (autoMode == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " + Secure.NIGHT_DISPLAY_AUTO_MODE); - } - autoMode = mContext.getResources().getInteger( - R.integer.config_defaultNightDisplayAutoMode); - } - - if (autoMode != AUTO_MODE_DISABLED - && autoMode != AUTO_MODE_CUSTOM - && autoMode != AUTO_MODE_TWILIGHT) { - Slog.e(TAG, "Invalid autoMode: " + autoMode); - autoMode = AUTO_MODE_DISABLED; - } - - return autoMode; + return mColorDisplayManager.getNightDisplayAutoMode(); } /** @@ -178,138 +91,64 @@ public final class ColorDisplayController { * never been set. */ public int getAutoModeRaw() { - return Secure.getIntForUser(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, - -1, mUserId); + return mColorDisplayManager.getNightDisplayAutoModeRaw(); } /** * Sets the current auto mode value controlling when Night display will be automatically - * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or - * {@link #AUTO_MODE_TWILIGHT}. + * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}. * * @param autoMode the new auto mode to use * @return {@code true} if new auto mode was set successfully */ public boolean setAutoMode(@AutoMode int autoMode) { - if (autoMode != AUTO_MODE_DISABLED - && autoMode != AUTO_MODE_CUSTOM - && autoMode != AUTO_MODE_TWILIGHT) { - throw new IllegalArgumentException("Invalid autoMode: " + autoMode); - } - - if (getAutoMode() != autoMode) { - Secure.putStringForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, - null, - mUserId); - getMetricsLogger().write(new LogMaker( - MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED) - .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(autoMode)); - } - - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId); + return mColorDisplayManager.setNightDisplayAutoMode(autoMode); } /** - * Returns the local time when Night display will be automatically activated when using - * {@link #AUTO_MODE_CUSTOM}. + * Returns the local time when Night display will be automatically activated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. */ public @NonNull LocalTime getCustomStartTime() { - int startTimeValue = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, -1, mUserId); - if (startTimeValue == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " - + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME); - } - startTimeValue = mContext.getResources().getInteger( - R.integer.config_defaultNightDisplayCustomStartTime); - } - - return LocalTime.ofSecondOfDay(startTimeValue / 1000); + return mColorDisplayManager.getNightDisplayCustomStartTime(); } /** - * Sets the local time when Night display will be automatically activated when using - * {@link #AUTO_MODE_CUSTOM}. + * Sets the local time when Night display will be automatically activated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. * * @param startTime the local time to automatically activate Night display * @return {@code true} if the new custom start time was set successfully */ public boolean setCustomStartTime(@NonNull LocalTime startTime) { - if (startTime == null) { - throw new IllegalArgumentException("startTime cannot be null"); - } - getMetricsLogger().write(new LogMaker( - MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) - .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(0)); - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toSecondOfDay() * 1000, mUserId); + return mColorDisplayManager.setNightDisplayCustomStartTime(startTime); } /** - * Returns the local time when Night display will be automatically deactivated when using - * {@link #AUTO_MODE_CUSTOM}. + * Returns the local time when Night display will be automatically deactivated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. */ public @NonNull LocalTime getCustomEndTime() { - int endTimeValue = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, -1, mUserId); - if (endTimeValue == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " - + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME); - } - endTimeValue = mContext.getResources().getInteger( - R.integer.config_defaultNightDisplayCustomEndTime); - } - - return LocalTime.ofSecondOfDay(endTimeValue / 1000); + return mColorDisplayManager.getNightDisplayCustomEndTime(); } /** - * Sets the local time when Night display will be automatically deactivated when using - * {@link #AUTO_MODE_CUSTOM}. + * Sets the local time when Night display will be automatically deactivated when using {@link + * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}. * * @param endTime the local time to automatically deactivate Night display * @return {@code true} if the new custom end time was set successfully */ public boolean setCustomEndTime(@NonNull LocalTime endTime) { - if (endTime == null) { - throw new IllegalArgumentException("endTime cannot be null"); - } - getMetricsLogger().write(new LogMaker( - MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED) - .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(1)); - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toSecondOfDay() * 1000, mUserId); + return mColorDisplayManager.setNightDisplayCustomEndTime(endTime); } /** * Returns the color temperature (in Kelvin) to tint the display when activated. */ public int getColorTemperature() { - int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId); - if (colorTemperature == -1) { - if (DEBUG) { - Slog.d(TAG, "Using default value for setting: " - + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE); - } - colorTemperature = getDefaultColorTemperature(); - } - final int minimumTemperature = getMinimumColorTemperature(); - final int maximumTemperature = getMaximumColorTemperature(); - if (colorTemperature < minimumTemperature) { - colorTemperature = minimumTemperature; - } else if (colorTemperature > maximumTemperature) { - colorTemperature = maximumTemperature; - } - - return colorTemperature; + return mColorDisplayManager.getNightDisplayColorTemperature(); } /** @@ -319,79 +158,14 @@ public final class ColorDisplayController { * @return {@code true} if new temperature was set successfully. */ public boolean setColorTemperature(int colorTemperature) { - return Secure.putIntForUser(mContext.getContentResolver(), - Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId); - } - - /** - * Get the current color mode from system properties, or return -1. - * - * See com.android.server.display.DisplayTransformManager. - */ - private @ColorMode int getCurrentColorModeFromSystemProperties() { - final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0); - if (displayColorSetting == 0) { - return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation")) - ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED; - } else if (displayColorSetting == 1) { - return COLOR_MODE_SATURATED; - } else if (displayColorSetting == 2) { - return COLOR_MODE_AUTOMATIC; - } else { - return -1; - } - } - - private boolean isColorModeAvailable(@ColorMode int colorMode) { - final int[] availableColorModes = mContext.getResources().getIntArray( - R.array.config_availableColorModes); - if (availableColorModes != null) { - for (int mode : availableColorModes) { - if (mode == colorMode) { - return true; - } - } - } - return false; + return mColorDisplayManager.setNightDisplayColorTemperature(colorTemperature); } /** * Get the current color mode. */ public int getColorMode() { - if (getAccessibilityTransformActivated()) { - if (isColorModeAvailable(COLOR_MODE_SATURATED)) { - return COLOR_MODE_SATURATED; - } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { - return COLOR_MODE_AUTOMATIC; - } - } - - int colorMode = System.getIntForUser(mContext.getContentResolver(), - System.DISPLAY_COLOR_MODE, -1, mUserId); - if (colorMode == -1) { - // There might be a system property controlling color mode that we need to respect; if - // not, this will set a suitable default. - colorMode = getCurrentColorModeFromSystemProperties(); - } - - // This happens when a color mode is no longer available (e.g., after system update or B&R) - // or the device does not support any color mode. - if (!isColorModeAvailable(colorMode)) { - if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) { - colorMode = COLOR_MODE_NATURAL; - } else if (colorMode == COLOR_MODE_SATURATED - && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { - colorMode = COLOR_MODE_AUTOMATIC; - } else if (colorMode == COLOR_MODE_AUTOMATIC - && isColorModeAvailable(COLOR_MODE_SATURATED)) { - colorMode = COLOR_MODE_SATURATED; - } else { - colorMode = -1; - } - } - - return colorMode; + return mColorDisplayManager.getColorMode(); } /** @@ -400,47 +174,21 @@ public final class ColorDisplayController { * @param colorMode the color mode */ public void setColorMode(@ColorMode int colorMode) { - if (!isColorModeAvailable(colorMode)) { - throw new IllegalArgumentException("Invalid colorMode: " + colorMode); - } - System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode, - mUserId); + mColorDisplayManager.setColorMode(colorMode); } /** * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated. */ public int getMinimumColorTemperature() { - return mContext.getResources().getInteger( - R.integer.config_nightDisplayColorTemperatureMin); + return ColorDisplayManager.getMinimumColorTemperature(mContext); } /** * Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated. */ public int getMaximumColorTemperature() { - return mContext.getResources().getInteger( - R.integer.config_nightDisplayColorTemperatureMax); - } - - /** - * Returns the default color temperature (in Kelvin) to tint the display when activated. - */ - public int getDefaultColorTemperature() { - return mContext.getResources().getInteger( - R.integer.config_nightDisplayColorTemperatureDefault); - } - - /** - * Returns true if any Accessibility color transforms are enabled. - */ - public boolean getAccessibilityTransformActivated() { - final ContentResolver cr = mContext.getContentResolver(); - return - Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, - 0, mUserId) == 1 - || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, - 0, mUserId) == 1; + return ColorDisplayManager.getMaximumColorTemperature(mContext); } private void onSettingChanged(@NonNull String setting) { @@ -465,13 +213,6 @@ public final class ColorDisplayController { case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE: mCallback.onColorTemperatureChanged(getColorTemperature()); break; - case System.DISPLAY_COLOR_MODE: - mCallback.onDisplayColorModeChanged(getColorMode()); - break; - case Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED: - case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED: - mCallback.onAccessibilityTransformChanged(getAccessibilityTransformActivated()); - break; } } } @@ -514,25 +255,10 @@ public final class ColorDisplayController { false /* notifyForDescendants */, mContentObserver, mUserId); cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE), false /* notifyForDescendants */, mContentObserver, mUserId); - cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE), - false /* notifyForDecendants */, mContentObserver, mUserId); - cr.registerContentObserver( - Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED), - false /* notifyForDecendants */, mContentObserver, mUserId); - cr.registerContentObserver( - Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED), - false /* notifyForDecendants */, mContentObserver, mUserId); } } } - private MetricsLogger getMetricsLogger() { - if (mMetricsLogger == null) { - mMetricsLogger = new MetricsLogger(); - } - return mMetricsLogger; - } - /** * Callback invoked whenever the Night display settings are changed. */ @@ -568,19 +294,5 @@ public final class ColorDisplayController { * @param colorTemperature the color temperature to tint the screen */ default void onColorTemperatureChanged(int colorTemperature) {} - - /** - * Callback invoked when the color mode changes. - * - * @param displayColorMode the color mode - */ - default void onDisplayColorModeChanged(int displayColorMode) {} - - /** - * Callback invoked when Accessibility color transforms change. - * - * @param state the state Accessibility color transforms (true of active) - */ - default void onAccessibilityTransformChanged(boolean state) {} } } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 7600dc9be447..8978496073e5 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -100,6 +100,7 @@ public final class InputMethodPrivilegedOperations { * @param backDisposition disposition flags * @see android.inputmethodservice.InputMethodService#IME_ACTIVE * @see android.inputmethodservice.InputMethodService#IME_VISIBLE + * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING */ diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index 9bacf9b6c2b9..f8483461c2d2 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -64,6 +64,9 @@ public class NetworkStatsFactory { private boolean mUseBpfStats; + // A persistent Snapshot since device start for eBPF stats + private final NetworkStats mPersistSnapshot; + // TODO: only do adjustments in NetworkStatsService and remove this. /** * (Stacked interface) -> (base interface) association for all connected ifaces since boot. @@ -135,6 +138,7 @@ public class NetworkStatsFactory { mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt"); mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats"); mUseBpfStats = useBpfStats; + mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1); } public NetworkStats readBpfNetworkStatsDev() throws IOException { @@ -268,6 +272,7 @@ public class NetworkStatsFactory { return stats; } + // TODO: delete the lastStats parameter private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces, int limitTag, NetworkStats lastStats) throws IOException { if (USE_NATIVE_PARSING) { @@ -278,16 +283,28 @@ public class NetworkStatsFactory { } else { stats = new NetworkStats(SystemClock.elapsedRealtime(), -1); } - if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, - limitIfaces, limitTag, mUseBpfStats) != 0) { - throw new IOException("Failed to parse network stats"); - } - if (SANITY_CHECK_NATIVE) { - final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, - limitIfaces, limitTag); - assertEquals(javaStats, stats); + if (mUseBpfStats) { + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL, + null, TAG_ALL, mUseBpfStats) != 0) { + throw new IOException("Failed to parse network stats"); + } + mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime()); + mPersistSnapshot.combineAllValues(stats); + NetworkStats result = mPersistSnapshot.clone(); + result.filter(limitUid, limitIfaces, limitTag); + return result; + } else { + if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, + limitIfaces, limitTag, mUseBpfStats) != 0) { + throw new IOException("Failed to parse network stats"); + } + if (SANITY_CHECK_NATIVE) { + final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid, + limitIfaces, limitTag); + assertEquals(javaStats, stats); + } + return stats; } - return stats; } else { return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag); } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index fd03b3f16348..da8605e645b4 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -28,6 +28,7 @@ import android.content.res.Resources; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.Network; +import android.net.ProxyInfo; import android.net.RouteInfo; import android.os.Parcel; import android.os.Parcelable; @@ -104,6 +105,7 @@ public class VpnConfig implements Parcelable { public boolean allowIPv4; public boolean allowIPv6; public Network[] underlyingNetworks; + public ProxyInfo proxyInfo; public void updateAllowedFamilies(InetAddress address) { if (address instanceof Inet4Address) { @@ -164,6 +166,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); out.writeTypedArray(underlyingNetworks, flags); + out.writeParcelable(proxyInfo, flags); } public static final Parcelable.Creator<VpnConfig> CREATOR = @@ -189,6 +192,7 @@ public class VpnConfig implements Parcelable { config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); + config.proxyInfo = in.readParcelable(null); return config; } @@ -220,6 +224,7 @@ public class VpnConfig implements Parcelable { .append(", allowIPv4=").append(allowIPv4) .append(", allowIPv6=").append(allowIPv6) .append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks)) + .append(", proxyInfo=").append(proxyInfo.toString()) .append("}") .toString(); } diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java index a676dacb0c49..b1a412871bd2 100644 --- a/core/java/com/android/internal/net/VpnInfo.java +++ b/core/java/com/android/internal/net/VpnInfo.java @@ -32,11 +32,11 @@ public class VpnInfo implements Parcelable { @Override public String toString() { - return "VpnInfo{" + - "ownerUid=" + ownerUid + - ", vpnIface='" + vpnIface + '\'' + - ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + - '}'; + return "VpnInfo{" + + "ownerUid=" + ownerUid + + ", vpnIface='" + vpnIface + '\'' + + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' + + '}'; } @Override diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 64543522893e..2c272dea073b 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -49,7 +49,7 @@ import java.util.function.ToDoubleFunction; * per thread, uid or call description. */ public class BinderCallsStats implements BinderInternal.Observer { - public static final boolean ENABLED_DEFAULT = false; + public static final boolean ENABLED_DEFAULT = true; public static final boolean DETAILED_TRACKING_DEFAULT = true; public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100; public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000; diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index 0b329d70f7af..c8d30b27d4dc 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -17,8 +17,9 @@ package com.android.internal.os; import android.app.ApplicationLoaders; +import android.app.LoadedApk; +import android.content.pm.ApplicationInfo; import android.net.LocalSocket; -import android.os.Build; import android.text.TextUtils; import android.util.Log; import android.webkit.WebViewFactory; @@ -66,6 +67,34 @@ class WebViewZygoteInit { } @Override + protected boolean canPreloadApp() { + return true; + } + + @Override + protected void handlePreloadApp(ApplicationInfo appInfo) { + Log.i(TAG, "Beginning application preload for " + appInfo.packageName); + LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false); + ClassLoader loader = loadedApk.getClassLoader(); + doPreload(loader, WebViewFactory.getWebViewLibrary(appInfo)); + + // Add the APK to the Zygote's list of allowed files for children. + Zygote.nativeAllowFileAcrossFork(appInfo.sourceDir); + if (appInfo.splitSourceDirs != null) { + for (String path : appInfo.splitSourceDirs) { + Zygote.nativeAllowFileAcrossFork(path); + } + } + if (appInfo.sharedLibraryFiles != null) { + for (String path : appInfo.sharedLibraryFiles) { + Zygote.nativeAllowFileAcrossFork(path); + } + } + + Log.i(TAG, "Application preload done"); + } + + @Override protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName, String cacheKey) { Log.i(TAG, "Beginning package preload"); @@ -76,16 +105,22 @@ class WebViewZygoteInit { ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader( packagePath, libsPath, cacheKey); - // Load the native library using WebViewLibraryLoader to share the RELRO data with other - // processes. - WebViewLibraryLoader.loadNativeLibrary(loader, libFileName); - // Add the APK to the Zygote's list of allowed files for children. String[] packageList = TextUtils.split(packagePath, File.pathSeparator); for (String packageEntry : packageList) { Zygote.nativeAllowFileAcrossFork(packageEntry); } + doPreload(loader, libFileName); + + Log.i(TAG, "Package preload done"); + } + + private void doPreload(ClassLoader loader, String libFileName) { + // Load the native library using WebViewLibraryLoader to share the RELRO data with other + // processes. + WebViewLibraryLoader.loadNativeLibrary(loader, libFileName); + // Once we have the classloader, look up the WebViewFactoryProvider implementation and // call preloadInZygote() on it to give it the opportunity to preload the native library // and perform any other initialisation work that should be shared among the children. @@ -114,8 +149,6 @@ class WebViewZygoteInit { } catch (IOException ioe) { throw new IllegalStateException("Error writing to command socket", ioe); } - - Log.i(TAG, "Package preload done"); } } diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index df89b265cd8e..24a08ca5b1e0 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -339,6 +339,8 @@ class ZygoteArguments { mMountExternal = Zygote.MOUNT_EXTERNAL_FULL; } else if (arg.equals("--mount-external-installer")) { mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER; + } else if (arg.equals("--mount-external-legacy")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY; } else if (arg.equals("--query-abi-list")) { mAbiListQuery = true; } else if (arg.equals("--get-pid")) { diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 97d5a657e5c4..2ee902ab6468 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -40,7 +40,7 @@ oneway interface IInputMethod { void unbindInput(); void startInput(in IBinder startInputToken, in IInputContext inputContext, int missingMethods, - in EditorInfo attribute, boolean restarting); + in EditorInfo attribute, boolean restarting, boolean preRenderImeViews); void createSession(in InputChannel channel, IInputSessionCallback callback); diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index ad51c4701d84..5de088397690 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -19,6 +19,7 @@ #include <hwui/Paint.h> #include <hwui/Bitmap.h> #include <renderthread/RenderProxy.h> +#include <utils/Color.h> #include <android_runtime/android_hardware_HardwareBuffer.h> @@ -602,6 +603,14 @@ static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) { return static_cast<jint>(bitmap->getGenerationID()); } +static jboolean Bitmap_isConfigF16(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + if (bitmap->info().colorType() == kRGBA_F16_SkColorType) { + return JNI_TRUE; + } + return JNI_FALSE; +} + static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); if (bitmap->info().alphaType() == kPremul_SkAlphaType) { @@ -1120,7 +1129,8 @@ static jobject Bitmap_createHardwareBitmap(JNIEnv* env, jobject, jobject graphic sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer)); // To support any color space, we need to pass an additional ColorSpace argument to // java Bitmap.createHardwareBitmap. - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB()); + SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat()); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, SkColorSpace::MakeSRGB()); if (!bitmap.get()) { ALOGW("failed to create hardware bitmap from graphic buffer"); return NULL; @@ -1133,7 +1143,8 @@ static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject har AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, hardwareBuffer); sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf)); - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, + SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat()); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, GraphicsJNI::getNativeColorSpace(colorSpacePtr)); if (!bitmap.get()) { ALOGW("failed to create hardware bitmap from hardware buffer"); @@ -1193,6 +1204,7 @@ static const JNINativeMethod gBitmapMethods[] = { { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong }, { "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes }, { "nativeConfig", "(J)I", (void*)Bitmap_config }, + { "nativeIsConfigF16", "(J)Z", (void*)Bitmap_isConfigF16 }, { "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha }, { "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied}, { "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha}, diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 7679c5b63274..dd7633ac6269 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -28,6 +28,7 @@ #include "SkBlurDrawLooper.h" #include "SkColorFilter.h" +#include "SkFont.h" #include "SkFontTypes.h" #include "SkMaskFilter.h" #include "SkPath.h" @@ -69,9 +70,21 @@ static JMetricsID gFontMetrics_fieldID; static jclass gFontMetricsInt_class; static JMetricsID gFontMetricsInt_fieldID; -static void defaultSettingsForAndroid(Paint* paint) { - // GlyphID encoding is required because we are using Harfbuzz shaping - paint->setTextEncoding(kGlyphID_SkTextEncoding); +static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count, + const SkPoint pos[], SkPath* dst) { + struct Rec { + SkPath* fDst; + const SkPoint* fPos; + } rec = { dst, pos }; + font.getPaths(glyphs, count, [](const SkPath* src, const SkMatrix& mx, void* ctx) { + Rec* rec = (Rec*)ctx; + if (src) { + SkMatrix tmp(mx); + tmp.postTranslate(rec->fPos->fX, rec->fPos->fY); + rec->fDst->addPath(*src, tmp); + } + rec->fPos += 1; + }, &rec); } namespace PaintGlue { @@ -88,18 +101,7 @@ namespace PaintGlue { } static jlong init(JNIEnv* env, jobject) { - static_assert(1 << 0 == SkPaint::kAntiAlias_Flag, "paint_flags_mismatch"); - static_assert(1 << 2 == SkPaint::kDither_Flag, "paint_flags_mismatch"); - static_assert(1 << 3 == SkPaint::kUnderlineText_ReserveFlag, "paint_flags_mismatch"); - static_assert(1 << 4 == SkPaint::kStrikeThruText_ReserveFlag, "paint_flags_mismatch"); - static_assert(1 << 5 == SkPaint::kFakeBoldText_Flag, "paint_flags_mismatch"); - static_assert(1 << 6 == SkPaint::kLinearText_Flag, "paint_flags_mismatch"); - static_assert(1 << 7 == SkPaint::kSubpixelText_Flag, "paint_flags_mismatch"); - static_assert(1 << 10 == SkPaint::kEmbeddedBitmapText_Flag, "paint_flags_mismatch"); - - Paint* obj = new Paint(); - defaultSettingsForAndroid(obj); - return reinterpret_cast<jlong>(obj); + return reinterpret_cast<jlong>(new Paint); } static jlong initWithPaint(JNIEnv* env, jobject clazz, jlong paintHandle) { @@ -288,10 +290,11 @@ namespace PaintGlue { pos[i].fX = x + layout.getX(i); pos[i].fY = y + layout.getY(i); } + const SkFont& font = paint->getSkFont(); if (start == 0) { - paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, path); + getPosTextPath(font, glyphs, end, pos, path); } else { - paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, &tmpPath); + getPosTextPath(font, glyphs + start, end - start, pos + start, &tmpPath); path->addPath(tmpPath); } } @@ -321,7 +324,6 @@ namespace PaintGlue { x += MinikinUtils::xOffsetForTextAlign(paint, layout); Paint::Align align = paint->getTextAlign(); paint->setTextAlign(Paint::kLeft_Align); - paint->setTextEncoding(kGlyphID_SkTextEncoding); GetTextFunctor f(layout, path, x, y, paint, glyphs, pos); MinikinUtils::forFontRun(layout, paint, f); paint->setTextAlign(align); @@ -584,20 +586,21 @@ namespace PaintGlue { const int kElegantDescent = -500; const int kElegantLeading = 0; Paint* paint = reinterpret_cast<Paint*>(paintHandle); + SkFont* font = &paint->getSkFont(); const Typeface* typeface = paint->getAndroidTypeface(); typeface = Typeface::resolveDefault(typeface); minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle); - float saveSkewX = paint->getTextSkewX(); - bool savefakeBold = paint->isFakeBoldText(); - MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery); - SkScalar spacing = paint->getFontMetrics(metrics); + float saveSkewX = font->getSkewX(); + bool savefakeBold = font->isEmbolden(); + MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery); + SkScalar spacing = font->getMetrics(metrics); // The populateSkPaint call may have changed fake bold / text skew // because we want to measure with those effects applied, so now // restore the original settings. - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + font->setSkewX(saveSkewX); + font->setEmbolden(savefakeBold); if (paint->getFamilyVariant() == minikin::FamilyVariant::ELEGANT) { - SkScalar size = paint->getTextSize(); + SkScalar size = font->getSize(); metrics->fTop = -size * kElegantTop / 2048; metrics->fBottom = -size * kElegantBottom / 2048; metrics->fAscent = -size * kElegantAscent / 2048; @@ -646,9 +649,7 @@ namespace PaintGlue { // ------------------ @CriticalNative --------------------------- static void reset(jlong objHandle) { - Paint* obj = reinterpret_cast<Paint*>(objHandle); - obj->reset(); - defaultSettingsForAndroid(obj); + reinterpret_cast<Paint*>(objHandle)->reset(); } static void assign(jlong dstPaintHandle, jlong srcPaintHandle) { @@ -657,31 +658,13 @@ namespace PaintGlue { *dst = *src; } - // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. - static const uint32_t sFilterBitmapFlag = 0x02; - static jint getFlags(jlong paintHandle) { - Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle); - uint32_t result = nativePaint->getFlags(); - result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away. - if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) { - result |= sFilterBitmapFlag; - } - return static_cast<jint>(result); + uint32_t flags = reinterpret_cast<Paint*>(paintHandle)->getJavaFlags(); + return static_cast<jint>(flags); } static void setFlags(jlong paintHandle, jint flags) { - Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle); - // Instead of modifying 0x02, change the filter level. - nativePaint->setFilterQuality(flags & sFilterBitmapFlag - ? kLow_SkFilterQuality - : kNone_SkFilterQuality); - // Don't pass through filter flag, which is no longer stored in paint's flags. - flags &= ~sFilterBitmapFlag; - // Use the existing value for 0x02. - const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag; - flags |= existing0x02Flag; - nativePaint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setJavaFlags(flags); } static jint getHinting(jlong paintHandle) { @@ -699,37 +682,23 @@ namespace PaintGlue { } static void setLinearText(jlong paintHandle, jboolean linearText) { - reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setLinearMetrics(linearText); } static void setSubpixelText(jlong paintHandle, jboolean subpixelText) { - reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSubpixel(subpixelText); } static void setUnderlineText(jlong paintHandle, jboolean underlineText) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - uint32_t flags = paint->getFlags(); - if (underlineText) { - flags |= Paint::kUnderlineText_ReserveFlag; - } else { - flags &= ~Paint::kUnderlineText_ReserveFlag; - } - paint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setUnderline(underlineText); } static void setStrikeThruText(jlong paintHandle, jboolean strikeThruText) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - uint32_t flags = paint->getFlags(); - if (strikeThruText) { - flags |= Paint::kStrikeThruText_ReserveFlag; - } else { - flags &= ~Paint::kStrikeThruText_ReserveFlag; - } - paint->setFlags(flags); + reinterpret_cast<Paint*>(paintHandle)->setStrikeThru(strikeThruText); } static void setFakeBoldText(jlong paintHandle, jboolean fakeBoldText) { - reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setEmbolden(fakeBoldText); } static void setFilterBitmap(jlong paintHandle, jboolean filterBitmap) { @@ -907,27 +876,29 @@ namespace PaintGlue { } static jfloat getTextSize(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize()); } static void setTextSize(jlong paintHandle, jfloat textSize) { - reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize); + if (textSize >= 0) { + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSize(textSize); + } } static jfloat getTextScaleX(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getScaleX()); } static void setTextScaleX(jlong paintHandle, jfloat scaleX) { - reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setScaleX(scaleX); } static jfloat getTextSkewX(jlong paintHandle) { - return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX()); + return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSkewX()); } static void setTextSkewX(jlong paintHandle, jfloat skewX) { - reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX); + reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSkewX(skewX); } static jfloat getLetterSpacing(jlong paintHandle) { @@ -979,7 +950,7 @@ namespace PaintGlue { if (metrics.hasUnderlinePosition(&position)) { return SkScalarToFloat(position); } else { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdUnderline_Top * textSize); } } @@ -991,18 +962,18 @@ namespace PaintGlue { if (metrics.hasUnderlineThickness(&thickness)) { return SkScalarToFloat(thickness); } else { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize); } } static jfloat getStrikeThruPosition(jlong paintHandle) { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize); } static jfloat getStrikeThruThickness(jlong paintHandle) { - const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize(); + const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize(); return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize); } diff --git a/core/jni/android/graphics/PaintFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp index 182b22b3c917..4fe9140572d3 100644 --- a/core/jni/android/graphics/PaintFilter.cpp +++ b/core/jni/android/graphics/PaintFilter.cpp @@ -21,6 +21,7 @@ #include "core_jni_helpers.h" +#include "hwui/Paint.h" #include "hwui/PaintFilter.h" #include "SkPaint.h" @@ -29,11 +30,15 @@ namespace android { class PaintFlagsFilter : public PaintFilter { public: PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) { - fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags); - fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags); + fClearFlags = static_cast<uint16_t>(clearFlags); + fSetFlags = static_cast<uint16_t>(setFlags); } void filter(SkPaint* paint) override { - paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags); + uint32_t flags = Paint::GetSkPaintJavaFlags(*paint); + Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags); + } + void filterFullPaint(Paint* paint) override { + paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags); } private: @@ -41,33 +46,6 @@ private: uint16_t fSetFlags; }; -// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality. -class CompatPaintFlagsFilter : public PaintFlagsFilter { -public: - CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality) - : PaintFlagsFilter(clearFlags, setFlags) - , fDesiredQuality(desiredQuality) { - } - - virtual void filter(SkPaint* paint) { - PaintFlagsFilter::filter(paint); - paint->setFilterQuality(fDesiredQuality); - } - -private: - const SkFilterQuality fDesiredQuality; -}; - -// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it. -static inline bool hadFiltering(jint& flags) { - // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. - static const uint32_t sFilterBitmapFlag = 0x02; - - const bool result = (flags & sFilterBitmapFlag) != 0; - flags &= ~sFilterBitmapFlag; - return result; -} - class PaintFilterGlue { public: @@ -78,29 +56,11 @@ public: static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz, jint clearFlags, jint setFlags) { + PaintFilter* filter = nullptr; if (clearFlags | setFlags) { - // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no - // longer has a Skia equivalent flag (instead it corresponds to - // calling setFilterQuality), and keep track of which group(s), if - // any, had the flag set. - const bool turnFilteringOn = hadFiltering(setFlags); - const bool turnFilteringOff = hadFiltering(clearFlags); - - PaintFilter* filter; - if (turnFilteringOn) { - // Turning filtering on overrides turning it off. - filter = new CompatPaintFlagsFilter(clearFlags, setFlags, - kLow_SkFilterQuality); - } else if (turnFilteringOff) { - filter = new CompatPaintFlagsFilter(clearFlags, setFlags, - kNone_SkFilterQuality); - } else { - filter = new PaintFlagsFilter(clearFlags, setFlags); - } - return reinterpret_cast<jlong>(filter); - } else { - return NULL; + filter = new PaintFlagsFilter(clearFlags, setFlags); } + return reinterpret_cast<jlong>(filter); } }; diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 318ec9b2ff0d..40529191a42c 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -1038,8 +1038,9 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode( // Continue I guess? } + SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat()); sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace); - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, cs); return bitmap::createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Premultiplied); } diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 53dde80edd89..4e486630adae 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -72,6 +72,7 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } + // Framework jars are allowed. static const char* kFrameworksPrefix = "/system/framework/"; static const char* kJarSuffix = ".jar"; if (android::base::StartsWith(path, kFrameworksPrefix) @@ -79,6 +80,13 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } + // Jars from the runtime apex are allowed. + static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/"; + if (android::base::StartsWith(path, kRuntimeApexPrefix) + && android::base::EndsWith(path, kJarSuffix)) { + return true; + } + // Whitelist files needed for Runtime Resource Overlay, like these: // /system/vendor/overlay/framework-res.apk // /system/vendor/overlay-subdir/pg/framework-res.apk diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index d79eb9402132..f06165cc7e00 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -451,6 +451,8 @@ message GlobalSettingsProto { optional SettingProto gup_blacklist = 11; // List of Apps that are allowed to use Game Driver package. optional SettingProto game_driver_whitelist = 12; + // ANGLE - List of Apps that can check ANGLE rules + optional SettingProto angle_whitelist = 13; } optional Gpu gpu = 59; diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index e68f9dbbc9b7..188769d930d1 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -31,6 +31,7 @@ import "frameworks/base/core/proto/android/os/persistablebundle.proto"; import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; +// Next tag: 21 message JobSchedulerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -139,9 +140,13 @@ message JobSchedulerServiceDumpProto { // The current limit on the number of concurrent JobServiceContext entries // we want to keep actively running a job. optional int32 max_active_jobs = 13; + + // Dump from JobConcurrencyManager. + optional JobConcurrencyManagerProto concurrency_manager = 20; } // A com.android.server.job.JobSchedulerService.Constants object. +// Next tag: 29 message ConstantsProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -273,7 +278,39 @@ message ConstantsProto { } optional QuotaController quota_controller = 24; - // Next tag: 26 + // Max number of jobs, when screen is ON. + optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_on = 26; + + // Max number of jobs, when screen is OFF. + optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_off = 27; + + // In this time after screen turns on, we increase job concurrency. + optional int32 screen_off_job_concurrency_increase_delay_ms = 28; +} + +// Next tag: 4 +message MaxJobCountsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Total number of jobs to run simultaneously. + optional int32 total_jobs = 1; + + // Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. + optional int32 max_bg = 2; + + // We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any + // pending, rather than always running the TOTAL number of FG jobs. + optional int32 min_bg = 3; +} + +// Next tag: 5 +message MaxJobCountsPerMemoryTrimLevelProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional MaxJobCountsProto normal = 1; + optional MaxJobCountsProto moderate = 2; + optional MaxJobCountsProto low = 3; + optional MaxJobCountsProto critical = 4; } message StateControllerProto { @@ -807,3 +844,46 @@ message JobStatusDumpProto { // Next tag: 28 } + +// Dump from com.android.server.job.JobConcurrencyManager. +// Next tag: 7 +message JobConcurrencyManagerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Whether the device is interactive (== screen on) now or not. + optional bool current_interactive = 1; + // Similar to current_interactive, screen on or not, but it takes into account the off timeout. + optional bool effective_interactive = 2; + // How many milliseconds have passed since the last screen on. (i.e. 1000 == 1 sec ago) + optional int64 time_since_last_screen_on_ms = 3; + // How many milliseconds have passed since the last screen off. (i.e. 1000 == 1 sec ago) + optional int64 time_since_last_screen_off_ms = 4; + // Current max number of jobs. + optional JobCountTrackerProto job_count_tracker = 5; + // Current memory trim level. + optional int32 memory_trim_level = 6; +} + +// Dump from com.android.server.job.JobConcurrencyManager.JobCountTracker. +// Next tag: 8 +message JobCountTrackerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Number of total jos that can run simultaneously. + optional int32 config_num_max_total_jobs = 1; + // Number of background jos that can run simultaneously. + optional int32 config_num_max_bg_jobs = 2; + // Out of total jobs, this many background jobs should be guaranteed to be executed, even if + // there are the config_num_max_total_jobs count of foreground jobs pending. + optional int32 config_num_min_bg_jobs = 3; + + // Number of running foreground jobs. + optional int32 num_running_fg_jobs = 4; + // Number of running background jobs. + optional int32 num_running_bg_jobs = 5; + + // Number of pending foreground jobs. + optional int32 num_pending_fg_jobs = 6; + // Number of pending background jobs. + optional int32 num_pending_bg_jobs = 7; +} diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index 00fae3d83ebc..367c54086ade 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -228,6 +228,15 @@ message UsbPortProto { repeated Mode supported_modes = 2; } +/* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */ +enum ContaminantPresenceStatus { + CONTAMINANT_STATUS_UNKNOWN = 0; + CONTAMINANT_STATUS_NOT_SUPPORTED = 1; + CONTAMINANT_STATUS_DISABLED = 2; + CONTAMINANT_STATUS_NOT_DETECTED = 3; + CONTAMINANT_STATUS_DETECTED = 4; +} + message UsbPortStatusProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -245,14 +254,6 @@ message UsbPortStatusProto { DATA_ROLE_DEVICE = 2; } - /* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */ - enum ContaminantPresenceStatus { - CONTAMINANT_STATUS_NOT_SUPPORTED = 0; - CONTAMINANT_STATUS_DISABLED = 1; - CONTAMINANT_STATUS_NOT_DETECTED = 2; - CONTAMINANT_STATUS_DETECTED = 3; - } - optional bool connected = 1; optional UsbPortProto.Mode current_mode = 2; optional PowerRole power_role = 3; diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 82460ec4ed8b..a8e64c6d8324 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -92,8 +92,7 @@ enum EventId { SET_UNINSTALL_BLOCKED = 67; SET_PACKAGES_SUSPENDED = 68; ON_LOCK_TASK_MODE_ENTERING = 69; - ADD_CROSS_PROFILE_CALENDAR_PACKAGE = 70; - REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE = 71; + SET_CROSS_PROFILE_CALENDAR_PACKAGES = 70; GET_USER_PASSWORD_COMPLEXITY_LEVEL = 72; INSTALL_SYSTEM_UPDATE = 73; INSTALL_SYSTEM_UPDATE_ERROR = 74; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f92df6a070c6..4a54bd775387 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2119,7 +2119,7 @@ <!-- ================================== --> <eat-comment /> - <!-- @SystemApi Allows an application to write to internal media storage + <!-- @SystemApi @TestApi Allows an application to write to internal media storage @hide --> <permission android:name="android.permission.WRITE_MEDIA_STORAGE" android:protectionLevel="signature|privileged" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index bbc55a3a2c0e..49f2c84335c5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -957,7 +957,7 @@ <!-- Default mode to control how Night display is automatically activated. One of the following values (see ColorDisplayController.java): 0 - AUTO_MODE_DISABLED - 1 - AUTO_MODE_CUSTOM + 1 - AUTO_MODE_CUSTOM_TIME 2 - AUTO_MODE_TWILIGHT --> <integer name="config_defaultNightDisplayAutoMode">0</integer> @@ -3729,9 +3729,6 @@ <!-- Whether or not the "SMS app service" feature is enabled --> <bool name="config_useSmsAppService">true</bool> - <!-- Component name for default assistant on this device --> - <string name="config_defaultAssistantComponentName">#+UNSET</string> - <!-- Class name for the InputEvent compatibility processor override. Empty string means use the default compatibility processor (android.view.InputEventCompatProcessor). --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 4235341b626b..ec1bac1a41d6 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2988,7 +2988,7 @@ </public-group> <public-group type="array" first-id="0x01070006"> - <!-- @hide @SystemApi --> + <!-- @hide @TestApi @SystemApi --> <public name="config_defaultRoleHolders" /> </public-group> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index cbb4cb218da0..d5561302fdc6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3528,8 +3528,6 @@ <java-symbol type="bool" name="config_useSmsAppService" /> - <java-symbol type="string" name="config_defaultAssistantComponentName" /> - <java-symbol type="id" name="transition_overlay_view_tag" /> <java-symbol type="dimen" name="rounded_corner_radius" /> diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 0fc3bd224fbf..8e8b07a9074b 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -49,6 +49,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := \ android.test.runner \ telephony-common \ + testables \ org.apache.http.legacy \ android.test.base \ android.test.mock \ diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 8604b0c48476..bdf3aa2563db 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -38,7 +38,6 @@ import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -501,7 +500,7 @@ public class TransactionParcelTests { } @Override - public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException { + public void updateHttpProxy() throws RemoteException { } @Override diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java index 7b92cf50ee21..300394d426e4 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -527,12 +527,14 @@ public class PackageParserTest { R.raw.com_android_tzdata); PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false); assertEquals("com.google.android.tzdata", pi.packageName); + assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertEquals(1, pi.getLongVersionCode()); assertEquals(1, pi.applicationInfo.longVersionCode); assertNull(pi.signingInfo); pi = PackageParser.generatePackageInfoFromApex(apexFile, true); assertEquals("com.google.android.tzdata", pi.packageName); + assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertEquals(1, pi.getLongVersionCode()); assertEquals(1, pi.applicationInfo.longVersionCode); assertNotNull(pi.signingInfo); diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index a010cb6576f2..a15dbc80d7db 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -390,9 +390,8 @@ public class SettingsBackupTest { Settings.Global.POWER_MANAGER_CONSTANTS, Settings.Global.PREFERRED_NETWORK_MODE, Settings.Global.PRIVATE_DNS_DEFAULT_MODE, - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED, - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, + Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED, Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED, Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS, Settings.Global.RADIO_BLUETOOTH, @@ -482,6 +481,7 @@ public class SettingsBackupTest { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, Settings.Global.GUP_DEV_ALL_APPS, Settings.Global.GUP_DEV_OPT_IN_APPS, Settings.Global.GUP_DEV_OPT_OUT_APPS, diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index d44745121a22..8f2109676dfb 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -16,15 +16,25 @@ package android.view; +import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; import static android.view.InsetsState.TYPE_TOP_BAR; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; - +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.view.WindowInsets.Type; +import android.view.WindowManager.BadTokenException; +import android.view.WindowManager.LayoutParams; +import android.widget.TextView; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; @@ -37,8 +47,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class InsetsControllerTest { - private InsetsController mController = new InsetsController(mock(ViewRootImpl.class)); - + private InsetsController mController; private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; @@ -47,6 +56,24 @@ public class InsetsControllerTest { mLeash = new SurfaceControl.Builder(mSession) .setName("testSurface") .build(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + Context context = InstrumentationRegistry.getTargetContext(); + // cannot mock ViewRootImpl since it's final. + ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); + try { + viewRootImpl.setView(new TextView(context), new LayoutParams(), null); + } catch (BadTokenException e) { + // activity isn't running, we will ignore BadTokenException. + } + mController = new InsetsController(viewRootImpl); + final Rect rect = new Rect(5, 5, 5, 5); + mController.calculateInsets( + false, + false, + new DisplayCutout( + Insets.of(10, 10, 10, 10), rect, rect, rect, rect), + rect, rect); + }); } @Test @@ -64,4 +91,39 @@ public class InsetsControllerTest { mController.onControlsChanged(new InsetsSourceControl[0]); assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl()); } + + @Test + public void testAnimationEndState() { + final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash); + final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash); + final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash); + + InsetsSourceControl[] controls = new InsetsSourceControl[3]; + controls[0] = navBar; + controls[1] = topBar; + controls[2] = ime; + mController.onControlsChanged(controls); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mController.show(Type.all()); + // quickly jump to final state by cancelling it. + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.hide(Type.all()); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.show(Type.ime()); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + mController.hide(Type.ime()); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + }); + } } diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index d57fa8f9f612..6a83c29b0943 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -20,6 +20,7 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.sideBars; import static android.view.WindowInsets.Type.topBar; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.graphics.Insets; @@ -73,4 +74,29 @@ public class WindowInsetsTest { assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar())); assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars())); } + + // TODO: Move this to CTS once API made public + @Test + public void visibility() { + Builder b = new WindowInsets.Builder(); + b.setInsets(sideBars(), Insets.of(0, 0, 0, 100)); + b.setInsets(ime(), Insets.of(0, 0, 0, 300)); + b.setVisible(sideBars(), true); + b.setVisible(ime(), true); + WindowInsets insets = b.build(); + assertTrue(insets.isVisible(sideBars())); + assertTrue(insets.isVisible(sideBars() | ime())); + assertFalse(insets.isVisible(sideBars() | topBar())); + } + + // TODO: Move this to CTS once API made public + @Test + public void consume_doesntChangeVisibility() { + Builder b = new WindowInsets.Builder(); + b.setInsets(ime(), Insets.of(0, 0, 0, 300)); + b.setVisible(ime(), true); + WindowInsets insets = b.build(); + insets = insets.consumeSystemWindowInsets(); + assertTrue(insets.isVisible(ime())); + } } diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index 780e15ab885e..5022e305ecc2 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -16,8 +16,8 @@ package android.view.textclassifier; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF; import static com.google.common.truth.Truth.assertThat; @@ -58,7 +58,7 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); + new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( @@ -81,7 +81,7 @@ public class ActionsSuggestionsHelperTest { .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_LOCAL) + new ConversationActions.Message.Builder(PERSON_USER_SELF) .setText("third") .build(); ConversationActions.Message fourthMessage = @@ -104,16 +104,16 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("first") .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_REMOTE) + new ConversationActions.Message.Builder(PERSON_USER_OTHERS) .setText("third") .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 4d78e4036e74..5e58f82038f1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -378,7 +378,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = @@ -407,7 +407,7 @@ public class TextClassifierTest { if (isTextClassifierDisabled()) return; ConversationActions.Message message = new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText("Where are you?") .build(); TextClassifier.EntityConfig typeConfig = diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java index b1b74160ecd5..73af56743b5f 100644 --- a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java @@ -18,9 +18,10 @@ package android.view.textclassifier.logging; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE; import static com.google.common.truth.Truth.assertThat; @@ -71,7 +72,8 @@ public class TextClassifierEventTronLoggerTest { new TextClassifierEvent.Builder( TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, TextClassifierEvent.TYPE_SMART_ACTION) - .setEntityType(ConversationAction.TYPE_CALL_PHONE) + .setEntityTypes(ConversationAction.TYPE_CALL_PHONE) + .setScore(0.5f) .setEventTime(EVENT_TIME) .setEventContext(textClassificationContext) .build(); @@ -83,15 +85,18 @@ public class TextClassifierEventTronLoggerTest { LogMaker logMaker = captor.getValue(); assertThat(logMaker.getCategory()).isEqualTo( CONVERSATION_ACTIONS); - assertThat(logMaker.getType()).isEqualTo( + assertThat(logMaker.getSubtype()).isEqualTo( ACTION_TEXT_SELECTION_SMART_SHARE); - assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE)) + assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE)) .isEqualTo(ConversationAction.TYPE_CALL_PHONE); + assertThat((float) logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE)) + .isWithin(0.00001f).of(0.5f); assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME)) .isEqualTo(EVENT_TIME); assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME); - assertThat(logMaker.getTaggedData(FIELD_SELECTION_WIDGET_TYPE)) + assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE)) .isEqualTo(WIDGET_TYPE); + } @Test diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index bfbdbc585e08..8636949943b7 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1711,20 +1711,22 @@ public final class Bitmap implements Parcelable { */ @Nullable public final ColorSpace getColorSpace() { - // A reconfigure can change the configuration and rgba16f is - // always linear scRGB at this time - if (getConfig() == Config.RGBA_F16) { - // Reset the color space for potential future reconfigurations - mColorSpace = null; - return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); - } - + checkRecycled("getColorSpace called on a recycled bitmap"); // Cache the color space retrieval since it can be fairly expensive if (mColorSpace == null) { - if (nativeIsSRGB(mNativePtr)) { + if (nativeIsConfigF16(mNativePtr)) { + // an F16 bitmaps is intended to always be linear extended, but due to + // inconsistencies in Bitmap.create() functions it is possible to have + // rendered into a bitmap in non-linear sRGB. + if (nativeIsSRGB(mNativePtr)) { + mColorSpace = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB); + } else { + mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + } + } else if (nativeIsSRGB(mNativePtr)) { mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); - } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) { - mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + } else if (nativeIsSRGBLinear(mNativePtr)) { + mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB); } else { float[] xyz = new float[9]; float[] params = new float[7]; @@ -2127,6 +2129,7 @@ public final class Bitmap implements Parcelable { private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color); private static native int nativeRowBytes(long nativeBitmap); private static native int nativeConfig(long nativeBitmap); + private static native boolean nativeIsConfigF16(long nativeBitmap); private static native int nativeGetPixel(long nativeBitmap, int x, int y); private static native void nativeGetPixels(long nativeBitmap, int[] pixels, diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3c35d9b33fc8..20303eba6667 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -562,7 +562,7 @@ std::string AssetManager2::GetLastResourceResolution() const { if (package != nullptr) { ToResourceName(last_resolution.type_string_ref, last_resolution.entry_string_ref, - package, + package->GetPackageName(), &resource_name); resource_name_string = ToFormattedResourceString(&resource_name); } @@ -607,15 +607,25 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) cons return false; } - const LoadedPackage* package = - apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); - if (package == nullptr) { + const uint8_t package_idx = package_ids_[get_package_id(resid)]; + if (package_idx == 0xff) { + LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", + get_package_id(resid), resid); + return false; + } + + const PackageGroup& package_group = package_groups_[package_idx]; + auto cookie_iter = std::find(package_group.cookies_.begin(), + package_group.cookies_.end(), cookie); + if (cookie_iter == package_group.cookies_.end()) { return false; } + long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter); + const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_; return ToResourceName(entry.type_string_ref, entry.entry_string_ref, - package, + package->GetPackageName(), out_name); } diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index 645984d85c34..c63dff8f9104 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -48,12 +48,12 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin !(has_type_separator && out_type->empty()); } -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name) { - out_name->package = package->GetPackageName().data(); - out_name->package_len = package->GetPackageName().size(); + out_name->package = package_name.data(); + out_name->package_len = package_name.size(); out_name->type = type_string_ref.string8(&out_name->type_len); out_name->type16 = nullptr; diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index eb6eb8e66175..e649940cdde1 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -28,12 +28,11 @@ namespace android { bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry); -// Convert a type_string_ref, entry_string_ref, and package -// to AssetManager2::ResourceName. Useful for getting -// resource name without re-running AssetManager2::FindEntry searches. -bool ToResourceName(StringPoolRef& type_string_ref, - StringPoolRef& entry_string_ref, - const LoadedPackage* package, +// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName. +// Useful for getting resource name without re-running AssetManager2::FindEntry searches. +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, AssetManager2::ResourceName* out_name); // Formats a ResourceName to "package:type/entry_name". diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 105dcd209bf7..447fdf5d306a 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -210,6 +210,16 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); } +TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({lib_one_assets_.get()}); + + AssetManager2::ResourceName name; + ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name)); + std::string formatted_name = ToFormattedResourceString(&name); + ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo"); +} + TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 56b1885de820..4c675133a6c1 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -62,10 +62,8 @@ DisplayInfo QueryDisplayInfo() { return displayInfo; } -static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut, - sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) { +static void queryWideColorGamutPreference(sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) { if (Properties::isolatedProcess) { - *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; *colorSpace = SkColorSpace::MakeSRGB(); *colorType = SkColorType::kN32_SkColorType; return; @@ -78,16 +76,13 @@ static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut, LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status); switch (wcgDataspace) { case ui::Dataspace::DISPLAY_P3: - *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut; *colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); break; case ui::Dataspace::V0_SCRGB: - *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; *colorSpace = SkColorSpace::MakeSRGB(); break; case ui::Dataspace::V0_SRGB: // when sRGB is returned, it means wide color gamut is not supported. - *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; *colorSpace = SkColorSpace::MakeSRGB(); break; default: @@ -112,7 +107,7 @@ DeviceInfo::DeviceInfo() { mMaxTextureSize = -1; #endif mDisplayInfo = QueryDisplayInfo(); - queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType); + queryWideColorGamutPreference(&mWideColorSpace, &mWideColorType); } int DeviceInfo::maxTextureSize() const { diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index 9bcc8e8a3dbe..2bab5d3596cf 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -38,7 +38,6 @@ public: // context or if you are using the HWUI_NULL_GPU int maxTextureSize() const; const DisplayInfo& displayInfo() const { return mDisplayInfo; } - SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; } sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; } SkColorType getWideColorType() const { return mWideColorType; } @@ -50,7 +49,6 @@ private: int mMaxTextureSize; DisplayInfo mDisplayInfo; - SkColorSpace::Gamut mWideColorGamut; sk_sp<SkColorSpace> mWideColorSpace; SkColorType mWideColorType; }; diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index 635d0ec66673..39bfcdd944a4 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -164,15 +164,11 @@ static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& sourc const SkImageInfo& info = source.info(); bitmap.allocPixels( SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr)); - bitmap.eraseColor(0); - if (info.colorType() == kRGBA_F16_SkColorType) { - // Drawing RGBA_F16 onto ARGB_8888 is not supported - source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), - bitmap.getPixels(), bitmap.rowBytes(), 0, 0); - } else { - SkCanvas canvas(bitmap); - canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); - } + + SkCanvas canvas(bitmap); + canvas.drawColor(0); + canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); + return bitmap; } } @@ -253,8 +249,8 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou eglDestroySyncKHR(display, fence); } - return Bitmap::createFrom(buffer.get(), bitmap.refColorSpace(), bitmap.alphaType(), - Bitmap::computePalette(bitmap)); + return Bitmap::createFrom(buffer.get(), bitmap.colorType(), bitmap.refColorSpace(), + bitmap.alphaType(), Bitmap::computePalette(bitmap)); } void HardwareBitmapUploader::terminate() { diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index cc62fdc76ef8..54a91f4ec06f 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -682,12 +682,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) { if (count <= 0 || paint.nothingToDraw()) return; - SkPaint paintCopy(paint); + Paint paintCopy(paint); if (mPaintFilter) { - mPaintFilter->filter(&paintCopy); + mPaintFilter->filterFullPaint(&paintCopy); } - SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy); - SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding); + const SkFont& font = paintCopy.getSkFont(); // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and // older. if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 && @@ -710,12 +709,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, const Paint& paint, const SkPath& path, size_t start, size_t end) { - SkPaint paintCopy(paint); + Paint paintCopy(paint); if (mPaintFilter) { - mPaintFilter->filter(&paintCopy); + mPaintFilter->filterFullPaint(&paintCopy); } - SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy); - SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding); + const SkFont& font = paintCopy.getSkFont(); const int N = end - start; SkTextBlobBuilder builder; diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 6e0258c9ecb2..3bbee18c6dd1 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -133,12 +133,11 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) } -sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace, - SkAlphaType alphaType, BitmapPalette palette) { - // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can - // view the format as RGBA8888. +sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, SkColorType colorType, + sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType, + BitmapPalette palette) { SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), - kRGBA_8888_SkColorType, alphaType, colorSpace); + colorType, alphaType, colorSpace); return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette)); } diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 2138040d9690..01e45166e0a3 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -72,6 +72,7 @@ public: * memory that is provided as an input param. */ static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer, + SkColorType colorType, sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType = kPremul_SkAlphaType, BitmapPalette palette = BitmapPalette::Unknown); diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 277148e3d556..523148672033 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -39,34 +39,28 @@ static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkSca } void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) { - uint32_t flags; - PaintFilter* paintFilter = getPaintFilter(); - if (paintFilter) { - SkPaint paintCopy(paint); - paintFilter->filter(&paintCopy); - flags = paintCopy.getFlags(); - } else { - flags = paint.getFlags(); - } - if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { + // paint has already been filtered by our caller, so we can ignore any filter + const bool strikeThru = paint.isStrikeThru(); + const bool underline = paint.isUnderline(); + if (strikeThru || underline) { const SkScalar left = x; const SkScalar right = x + length; - if (flags & SkPaint::kUnderlineText_ReserveFlag) { + const float textSize = paint.getSkFont().getSize(); + if (underline) { SkFontMetrics metrics; - paint.getFontMetrics(&metrics); + paint.getSkFont().getMetrics(&metrics); SkScalar position; if (!metrics.hasUnderlinePosition(&position)) { - position = paint.getTextSize() * Paint::kStdUnderline_Top; + position = textSize * Paint::kStdUnderline_Top; } SkScalar thickness; if (!metrics.hasUnderlineThickness(&thickness)) { - thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; + thickness = textSize * Paint::kStdUnderline_Thickness; } const SkScalar top = y + position; drawStroke(left, right, top, thickness, paint, this); } - if (flags & SkPaint::kStrikeThruText_ReserveFlag) { - const float textSize = paint.getTextSize(); + if (strikeThru) { const float position = textSize * Paint::kStdStrikeThru_Top; const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; const SkScalar top = y + position; @@ -75,19 +69,19 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa } } -static void simplifyPaint(int color, SkPaint* paint) { +static void simplifyPaint(int color, Paint* paint) { paint->setColor(color); paint->setShader(nullptr); paint->setColorFilter(nullptr); paint->setLooper(nullptr); - paint->setStrokeWidth(4 + 0.04 * paint->getTextSize()); + paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); } class DrawTextFunctor { public: - DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const SkPaint& paint, float x, + DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, float y, minikin::MinikinRect& bounds, float totalAdvance) : layout(layout) , canvas(canvas) @@ -123,14 +117,14 @@ public: bool darken = channelSum < (128 * 3); // outline - SkPaint outlinePaint(paint); + Paint outlinePaint(paint); simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance); // inner - SkPaint innerPaint(paint); + Paint innerPaint(paint); simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); innerPaint.setStyle(SkPaint::kFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop, @@ -145,7 +139,7 @@ public: private: const minikin::Layout& layout; Canvas* canvas; - const SkPaint& paint; + const Paint& paint; float x; float y; minikin::MinikinRect& bounds; diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 84292c8768c1..375f5bc9df37 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -17,8 +17,9 @@ #include "MinikinSkia.h" #include <SkFontDescriptor.h> +#include <SkFont.h> +#include <SkFontMetrics.h> #include <SkFontMgr.h> -#include <SkPaint.h> #include <SkTypeface.h> #include <log/log.h> @@ -40,25 +41,24 @@ MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontDat , mAxes(axes) , mFilePath(filePath) {} -static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint, - const minikin::MinikinPaint& paint, - const minikin::FontFakery& fakery) { - skPaint->setTextEncoding(kGlyphID_SkTextEncoding); - skPaint->setTextSize(paint.size); - skPaint->setTextScaleX(paint.scaleX); - skPaint->setTextSkewX(paint.skewX); - MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags); +static void MinikinFontSkia_SetSkiaFont(const minikin::MinikinFont* font, SkFont* skFont, + const minikin::MinikinPaint& paint, + const minikin::FontFakery& fakery) { + skFont->setSize(paint.size); + skFont->setScaleX(paint.scaleX); + skFont->setSkewX(paint.skewX); + MinikinFontSkia::unpackFontFlags(skFont, paint.fontFlags); // Apply font fakery on top of user-supplied flags. - MinikinFontSkia::populateSkPaint(skPaint, font, fakery); + MinikinFontSkia::populateSkFont(skFont, font, fakery); } float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkScalar skWidth; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, &skWidth); #ifdef VERBOSE ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth); #endif @@ -68,11 +68,11 @@ float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::Mi void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkRect skBounds; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, nullptr, &skBounds); bounds->mLeft = skBounds.fLeft; bounds->mTop = skBounds.fTop; bounds->mRight = skBounds.fRight; @@ -82,10 +82,10 @@ void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); + SkFont skFont; + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); SkFontMetrics metrics; - skPaint.getFontMetrics(&metrics); + skFont.getMetrics(&metrics); extent->ascent = metrics.fAscent; extent->descent = metrics.fDescent; } @@ -137,28 +137,36 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( ttcIndex, variations); } -uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) { - uint32_t flags = paint->getFlags(); - unsigned hinting = static_cast<unsigned>(paint->getHinting()); - // select only flags that might affect text layout - flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag | - SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag | - SkPaint::kAutoHinting_Flag); - flags |= (hinting << 16); +// hinting<<16 | edging<<8 | bools:5bits +uint32_t MinikinFontSkia::packFontFlags(const SkFont& font) { + uint32_t flags = (unsigned)font.getHinting() << 16; + flags |= (unsigned)font.getEdging() << 8; + flags |= font.isEmbolden() << minikin::Embolden_Shift; + flags |= font.isLinearMetrics() << minikin::LinearMetrics_Shift; + flags |= font.isSubpixel() << minikin::Subpixel_Shift; + flags |= font.isEmbeddedBitmaps() << minikin::EmbeddedBitmaps_Shift; + flags |= font.isForceAutoHinting() << minikin::ForceAutoHinting_Shift; return flags; } -void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) { - paint->setFlags(paintFlags & SkPaint::kAllFlags); - paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16)); +void MinikinFontSkia::unpackFontFlags(SkFont* font, uint32_t flags) { + // We store hinting in the top 16 bits (only need 2 of them) + font->setHinting((SkFontHinting)(flags >> 16)); + // We store edging in bits 8:15 (only need 2 of them) + font->setEdging((SkFont::Edging)((flags >> 8) & 0xFF)); + font->setEmbolden( (flags & minikin::Embolden_Flag) != 0); + font->setLinearMetrics( (flags & minikin::LinearMetrics_Flag) != 0); + font->setSubpixel( (flags & minikin::Subpixel_Flag) != 0); + font->setEmbeddedBitmaps( (flags & minikin::EmbeddedBitmaps_Flag) != 0); + font->setForceAutoHinting((flags & minikin::ForceAutoHinting_Flag) != 0); } -void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font, - minikin::FontFakery fakery) { - paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); - paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold()); +void MinikinFontSkia::populateSkFont(SkFont* skFont, const MinikinFont* font, + minikin::FontFakery fakery) { + skFont->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); + skFont->setEmbolden(skFont->isEmbolden() || fakery.isFakeBold()); if (fakery.isFakeItalic()) { - paint->setTextSkewX(paint->getTextSkewX() - 0.25f); + skFont->setSkewX(skFont->getSkewX() - 0.25f); } } } diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h index 55576b7bfa4e..ad46b2391cac 100644 --- a/libs/hwui/hwui/MinikinSkia.h +++ b/libs/hwui/hwui/MinikinSkia.h @@ -21,7 +21,7 @@ #include <cutils/compiler.h> #include <minikin/MinikinFont.h> -class SkPaint; +class SkFont; class SkTypeface; namespace android { @@ -54,12 +54,12 @@ public: std::shared_ptr<minikin::MinikinFont> createFontWithVariation( const std::vector<minikin::FontVariation>&) const; - static uint32_t packPaintFlags(const SkPaint* paint); - static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags); + static uint32_t packFontFlags(const SkFont&); + static void unpackFontFlags(SkFont*, uint32_t fontFlags); // set typeface and fake bold/italic parameters - static void populateSkPaint(SkPaint* paint, const minikin::MinikinFont* font, - minikin::FontFakery fakery); + static void populateSkFont(SkFont*, const minikin::MinikinFont* font, + minikin::FontFakery fakery); private: sk_sp<SkTypeface> mTypeface; diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index ba240feb4f41..733f8e415270 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -30,16 +30,17 @@ namespace android { minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, const Typeface* typeface) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); + const SkFont& font = paint->getSkFont(); minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection); /* Prepare minikin Paint */ minikinPaint.size = - paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize()); - minikinPaint.scaleX = paint->getTextScaleX(); - minikinPaint.skewX = paint->getTextSkewX(); + font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize()); + minikinPaint.scaleX = font.getScaleX(); + minikinPaint.skewX = font.getSkewX(); minikinPaint.letterSpacing = paint->getLetterSpacing(); minikinPaint.wordSpacing = paint->getWordSpacing(); - minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint); + minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font); minikinPaint.localeListId = paint->getMinikinLocaleListId(); minikinPaint.familyVariant = paint->getFamilyVariant(); minikinPaint.fontStyle = resolvedFace->fStyle; diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index d27d54454ea0..cbf409504675 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -63,27 +63,29 @@ public: // f is a functor of type void f(size_t start, size_t end); template <typename F> ANDROID_API static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) { - float saveSkewX = paint->getTextSkewX(); - bool savefakeBold = paint->isFakeBoldText(); + float saveSkewX = paint->getSkFont().getSkewX(); + bool savefakeBold = paint->getSkFont().isEmbolden(); const minikin::MinikinFont* curFont = nullptr; size_t start = 0; size_t nGlyphs = layout.nGlyphs(); for (size_t i = 0; i < nGlyphs; i++) { const minikin::MinikinFont* nextFont = layout.getFont(i); if (i > 0 && nextFont != curFont) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, i); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); start = i; } curFont = nextFont; } if (nGlyphs > start) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, nGlyphs); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); } } }; diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 92ffda9486bb..601b3c23cc1f 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -21,6 +21,7 @@ #include <cutils/compiler.h> +#include <SkFont.h> #include <SkPaint.h> #include <string> @@ -46,7 +47,6 @@ public: Paint(); Paint(const Paint& paint); - Paint(const SkPaint& paint); // NOLINT(google-explicit-constructor) ~Paint(); Paint& operator=(const Paint& other); @@ -54,6 +54,17 @@ public: friend bool operator==(const Paint& a, const Paint& b); friend bool operator!=(const Paint& a, const Paint& b) { return !(a == b); } + SkFont& getSkFont() { return mFont; } + const SkFont& getSkFont() const { return mFont; } + + // These shadow the methods on SkPaint, but we need to so we can keep related + // attributes in-sync. + + void reset(); + void setAntiAlias(bool); + + // End method shadowing + void setLetterSpacing(float letterSpacing) { mLetterSpacing = letterSpacing; } float getLetterSpacing() const { return mLetterSpacing; } @@ -94,7 +105,31 @@ public: Align getTextAlign() const { return mAlign; } void setTextAlign(Align align) { mAlign = align; } + bool isStrikeThru() const { return mStrikeThru; } + void setStrikeThru(bool st) { mStrikeThru = st; } + + bool isUnderline() const { return mUnderline; } + void setUnderline(bool u) { mUnderline = u; } + + bool isDevKern() const { return mDevKern; } + void setDevKern(bool d) { mDevKern = d; } + + // The Java flags (Paint.java) no longer fit into the native apis directly. + // These methods handle converting to and from them and the native representations + // in android::Paint. + + uint32_t getJavaFlags() const; + void setJavaFlags(uint32_t); + + // Helpers that return or apply legacy java flags to SkPaint, ignoring all flags + // that are meant for SkFont or Paint (e.g. underline, strikethru) + // The only respected flags are : [ antialias, dither, filterBitmap ] + static uint32_t GetSkPaintJavaFlags(const SkPaint&); + static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags); + private: + SkFont mFont; + float mLetterSpacing = 0; float mWordSpacing = 0; std::string mFontFeatureSettings; @@ -107,6 +142,9 @@ private: // nullptr is valid: it means the default typeface. const Typeface* mTypeface = nullptr; Align mAlign = kLeft_Align; + bool mStrikeThru = false; + bool mUnderline = false; + bool mDevKern = false; }; } // namespace android diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h index bf5627eac229..0e7b61977000 100644 --- a/libs/hwui/hwui/PaintFilter.h +++ b/libs/hwui/hwui/PaintFilter.h @@ -12,6 +12,7 @@ public: * The implementation may modify the paint as they wish. */ virtual void filter(SkPaint*) = 0; + virtual void filterFullPaint(Paint*) = 0; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index bdbf5cacaaf0..d2903f08af15 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -24,10 +24,16 @@ Paint::Paint() , mWordSpacing(0) , mFontFeatureSettings() , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} + , mFamilyVariant(minikin::FamilyVariant::DEFAULT) { + // SkPaint::antialiasing defaults to false, but + // SkFont::edging defaults to kAntiAlias. To keep them + // insync, we manually set the font to kAilas. + mFont.setEdging(SkFont::Edging::kAlias); +} Paint::Paint(const Paint& paint) : SkPaint(paint) + , mFont(paint.mFont) , mLetterSpacing(paint.mLetterSpacing) , mWordSpacing(paint.mWordSpacing) , mFontFeatureSettings(paint.mFontFeatureSettings) @@ -35,20 +41,17 @@ Paint::Paint(const Paint& paint) , mFamilyVariant(paint.mFamilyVariant) , mHyphenEdit(paint.mHyphenEdit) , mTypeface(paint.mTypeface) - , mAlign(paint.mAlign) {} + , mAlign(paint.mAlign) + , mStrikeThru(paint.mStrikeThru) + , mUnderline(paint.mUnderline) + , mDevKern(paint.mDevKern) {} -Paint::Paint(const SkPaint& paint) - : SkPaint(paint) - , mLetterSpacing(0) - , mWordSpacing(0) - , mFontFeatureSettings() - , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} Paint::~Paint() {} Paint& Paint::operator=(const Paint& other) { SkPaint::operator=(other); + mFont = other.mFont; mLetterSpacing = other.mLetterSpacing; mWordSpacing = other.mWordSpacing; mFontFeatureSettings = other.mFontFeatureSettings; @@ -57,15 +60,136 @@ Paint& Paint::operator=(const Paint& other) { mHyphenEdit = other.mHyphenEdit; mTypeface = other.mTypeface; mAlign = other.mAlign; + mStrikeThru = other.mStrikeThru; + mUnderline = other.mUnderline; + mDevKern = other.mDevKern; return *this; } bool operator==(const Paint& a, const Paint& b) { return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && + a.mFont == b.mFont && a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings && a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && - a.mTypeface == b.mTypeface && a.mAlign == b.mAlign; + a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && + a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline && + a.mDevKern == b.mDevKern; +} + +void Paint::reset() { + SkPaint::reset(); + + mFont = SkFont(); + mFont.setEdging(SkFont::Edging::kAlias); + + mStrikeThru = false; + mUnderline = false; + mDevKern = false; +} + +void Paint::setAntiAlias(bool aa) { + // Java does not support/understand subpixel(lcd) antialiasing + SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias); + // JavaPaint antialiasing affects both the SkPaint and SkFont settings. + SkPaint::setAntiAlias(aa); + mFont.setEdging(aa ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias); +} + +////////////////// Java flags compatibility ////////////////// + +/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really + match up with skia anymore, so we have to do some shuffling in get/set flags() + + 3 flags apply to SkPaint (antialias, dither, filter -> enum) + 5 flags (merged with antialias) are for SkFont + 2 flags are for minikin::Paint (underline and strikethru) +*/ + +// flags relating to SkPaint +static const uint32_t sAntiAliasFlag = 0x01; // affects paint and font-edging +static const uint32_t sFilterBitmapFlag = 0x02; // maps to enum +static const uint32_t sDitherFlag = 0x04; +// flags relating to SkFont +static const uint32_t sFakeBoldFlag = 0x020; +static const uint32_t sLinearMetrics = 0x040; +static const uint32_t sSubpixelMetrics = 0x080; +static const uint32_t sEmbeddedBitmaps = 0x400; +static const uint32_t sForceAutoHinting = 0x800; +// flags related to minikin::Paint +static const uint32_t sUnderlineFlag = 0x08; +static const uint32_t sStrikeThruFlag = 0x10; +// flags no longer supported on native side (but mirrored for compatibility) +static const uint32_t sDevKernFlag = 0x100; + +static uint32_t paintToLegacyFlags(const SkPaint& paint) { + uint32_t flags = 0; + flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag; + flags |= -(int)paint.isDither() & sDitherFlag; + if (paint.getFilterQuality() != kNone_SkFilterQuality) { + flags |= sFilterBitmapFlag; + } + return flags; } + +static uint32_t fontToLegacyFlags(const SkFont& font) { + uint32_t flags = 0; + flags |= -(int)font.isEmbolden() & sFakeBoldFlag; + flags |= -(int)font.isLinearMetrics() & sLinearMetrics; + flags |= -(int)font.isSubpixel() & sSubpixelMetrics; + flags |= -(int)font.isEmbeddedBitmaps() & sEmbeddedBitmaps; + flags |= -(int)font.isForceAutoHinting() & sForceAutoHinting; + return flags; +} + +static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) { + paint->setAntiAlias((flags & sAntiAliasFlag) != 0); + paint->setDither ((flags & sDitherFlag) != 0); + + if (flags & sFilterBitmapFlag) { + paint->setFilterQuality(kLow_SkFilterQuality); + } else { + paint->setFilterQuality(kNone_SkFilterQuality); + } +} + +static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) { + font->setEmbolden ((flags & sFakeBoldFlag) != 0); + font->setLinearMetrics ((flags & sLinearMetrics) != 0); + font->setSubpixel ((flags & sSubpixelMetrics) != 0); + font->setEmbeddedBitmaps ((flags & sEmbeddedBitmaps) != 0); + font->setForceAutoHinting((flags & sForceAutoHinting) != 0); + + if (flags & sAntiAliasFlag) { + font->setEdging(SkFont::Edging::kAntiAlias); + } else { + font->setEdging(SkFont::Edging::kAlias); + } +} + +uint32_t Paint::GetSkPaintJavaFlags(const SkPaint& paint) { + return paintToLegacyFlags(paint); +} + +void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) { + applyLegacyFlagsToPaint(flags, paint); +} + +uint32_t Paint::getJavaFlags() const { + uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont); + flags |= -(int)mStrikeThru & sStrikeThruFlag; + flags |= -(int)mUnderline & sUnderlineFlag; + flags |= -(int)mDevKern & sDevKernFlag; + return flags; +} + +void Paint::setJavaFlags(uint32_t flags) { + applyLegacyFlagsToPaint(flags, this); + applyLegacyFlagsToFont(flags, &mFont); + mStrikeThru = (flags & sStrikeThruFlag) != 0; + mUnderline = (flags & sUnderlineFlag) != 0; + mDevKern = (flags & sDevKernFlag) != 0; +} + } // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 60c805741058..a1b2b18195bc 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -138,6 +138,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { info.width = fboSize.width(); info.height = fboSize.height(); mat4.asColMajorf(&info.transform[0]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); // ensure that the framebuffer that the webview will render into is bound before we clear // the stencil and/or draw the functor. diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 1661905eff57..8508274676fd 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -31,8 +31,8 @@ namespace skiapipeline { // Cache size limits. static const size_t maxKeySize = 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 512 * 1024; +static const size_t maxValueSize = 512 * 1024; +static const size_t maxTotalSize = 1024 * 1024; ShaderCache::ShaderCache() { // There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header. diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index cfbb9956d3dc..570e895a012d 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -167,7 +167,7 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh if (surface) { mRenderThread.requireGlContext(); - auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut); + auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace); if (!newSurface) { return false; } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 47c90948bbbe..a00a36f93501 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -476,11 +476,9 @@ void SkiaPipeline::dumpResourceCacheUsage() const { void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { if (colorMode == ColorMode::SRGB) { mSurfaceColorType = SkColorType::kN32_SkColorType; - mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut; mSurfaceColorSpace = SkColorSpace::MakeSRGB(); } else if (colorMode == ColorMode::WideColorGamut) { mSurfaceColorType = DeviceInfo::get()->getWideColorType(); - mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut(); mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace(); } else { LOG_ALWAYS_FATAL("Unreachable: unsupported color mode."); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index e9957df95f10..7381e0417a2d 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -116,7 +116,6 @@ protected: renderthread::RenderThread& mRenderThread; SkColorType mSurfaceColorType; - SkColorSpace::Gamut mSurfaceColorGamut; sk_sp<SkColorSpace> mSurfaceColorSpace; private: diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 53495a7d62c0..d0fe022616c5 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -129,7 +129,7 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh setSurfaceColorProperties(colorMode); if (surface) { mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, - mSurfaceColorGamut, mSurfaceColorType); + mSurfaceColorType); } return mVkSurface != nullptr; @@ -149,20 +149,8 @@ void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* func sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread, SkBitmap& skBitmap) { - // TODO: implement this function for Vulkan pipeline - // code below is a hack to avoid crashing because of missing HW Bitmap support - sp<GraphicBuffer> buffer = new GraphicBuffer( - skBitmap.info().width(), skBitmap.info().height(), PIXEL_FORMAT_RGBA_8888, - GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | - GraphicBuffer::USAGE_SW_READ_NEVER, - std::string("SkiaVulkanPipeline::allocateHardwareBitmap pid [") + - std::to_string(getpid()) + "]"); - status_t error = buffer->initCheck(); - if (error < 0) { - ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()"); - return nullptr; - } - return Bitmap::createFrom(buffer, skBitmap.refColorSpace()); + LOG_ALWAYS_FATAL("Unimplemented"); + return nullptr; } } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index c3563dbfd3cd..706325f00bd2 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -132,6 +132,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { info.width = mFBInfo.width(); info.height = mFBInfo.height(); mat4.asColMajorf(&info.transform[0]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); glViewport(0, 0, info.width, info.height); @@ -179,8 +180,8 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { canvas->resetMatrix(); auto functorImage = SkImage::MakeFromAHardwareBuffer( - reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, nullptr, - kBottomLeft_GrSurfaceOrigin); + reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, + canvas->imageInfo().refColorSpace(), kBottomLeft_GrSurfaceOrigin); canvas->drawImage(functorImage, 0, 0, &paint); canvas->restore(); } diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h index 9e1bb8e8e548..501b8df9bc36 100644 --- a/libs/hwui/private/hwui/DrawGlInfo.h +++ b/libs/hwui/private/hwui/DrawGlInfo.h @@ -17,6 +17,8 @@ #ifndef ANDROID_HWUI_DRAW_GL_INFO_H #define ANDROID_HWUI_DRAW_GL_INFO_H +#include <SkColorSpace.h> + namespace android { namespace uirenderer { @@ -41,6 +43,9 @@ struct DrawGlInfo { // Input: current transform matrix, in OpenGL format float transform[16]; + // Input: Color space. + const SkColorSpace* color_space_ptr; + // Output: dirty region to redraw float dirtyLeft; float dirtyTop; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 8cd97ed20215..2cc3f362e172 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -132,11 +132,13 @@ void EglManager::initialize() { createPBufferSurface(); makeCurrent(mPBufferSurface, nullptr, /* force */ true); - SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut(); + skcms_Matrix3x3 wideColorGamut; + LOG_ALWAYS_FATAL_IF(!DeviceInfo::get()->getWideColorSpace()->toXYZD50(&wideColorGamut), + "Could not get gamut matrix from wideColorSpace"); bool hasWideColorSpaceExtension = false; - if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) { + if (memcmp(&wideColorGamut, &SkNamedGamut::kDCIP3, sizeof(wideColorGamut)) == 0) { hasWideColorSpaceExtension = EglExtensions.displayP3; - } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) { + } else if (memcmp(&wideColorGamut, &SkNamedGamut::kSRGB, sizeof(wideColorGamut)) == 0) { hasWideColorSpaceExtension = EglExtensions.scRGB; } else { LOG_ALWAYS_FATAL("Unsupported wide color space."); @@ -297,7 +299,7 @@ void EglManager::createPBufferSurface() { Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode, - SkColorSpace::Gamut colorGamut) { + sk_sp<SkColorSpace> colorSpace) { LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized"); bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport && @@ -330,15 +332,15 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, if (EglExtensions.glColorSpace) { attribs[0] = EGL_GL_COLORSPACE_KHR; if (wideColorGamut) { - switch (colorGamut) { - case SkColorSpace::Gamut::kDCIP3_D65_Gamut: - attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; - break; - case SkColorSpace::Gamut::kSRGB_Gamut: - attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; - break; - default: - LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); + skcms_Matrix3x3 colorGamut; + LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut), + "Could not get gamut matrix from color space"); + if (memcmp(&colorGamut, &SkNamedGamut::kDCIP3, sizeof(colorGamut)) == 0) { + attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; + } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) { + attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; + } else { + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); } } else { attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 4dd90961b4f7..27d41d26a73a 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -49,7 +49,7 @@ public: bool hasEglContext(); Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode, - SkColorSpace::Gamut colorGamut); + sk_sp<SkColorSpace> colorSpace); void destroySurface(EGLSurface surface); void destroy(); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 5c6cb9ad43db..1e7520216d66 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -516,10 +516,9 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) { if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) { ColorMode colorMode = surface->mColorMode; sk_sp<SkColorSpace> colorSpace = surface->mColorSpace; - SkColorSpace::Gamut colorGamut = surface->mColorGamut; SkColorType colorType = surface->mColorType; destroySurface(surface); - *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType); + *surfaceOut = createSurface(window, colorMode, colorSpace, colorType); surface = *surfaceOut; } @@ -841,9 +840,12 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { } if (surface->mColorMode == ColorMode::WideColorGamut) { - if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) { + skcms_Matrix3x3 surfaceGamut; + LOG_ALWAYS_FATAL_IF(!surface->mColorSpace->toXYZD50(&surfaceGamut), + "Could not get gamut matrix from color space"); + if (memcmp(&surfaceGamut, &SkNamedGamut::kSRGB, sizeof(surfaceGamut)) == 0) { colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT; - } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) { + } else if (memcmp(&surfaceGamut, &SkNamedGamut::kDCIP3, sizeof(surfaceGamut)) == 0) { colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT; } else { LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); @@ -922,7 +924,6 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode, sk_sp<SkColorSpace> surfaceColorSpace, - SkColorSpace::Gamut surfaceColorGamut, SkColorType surfaceColorType) { initialize(); @@ -931,7 +932,7 @@ VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode col } VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace, - surfaceColorGamut, surfaceColorType); + surfaceColorType); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index b06eb82dac79..abe78efc9174 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -39,9 +39,9 @@ class RenderThread; class VulkanSurface { public: VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace, - SkColorSpace::Gamut colorGamut, SkColorType colorType) + SkColorType colorType) : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace), - mColorGamut(colorGamut), mColorType(colorType) {} + mColorType(colorType) {} sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; } @@ -90,7 +90,6 @@ private: int mWindowWidth = 0; int mWindowHeight = 0; sk_sp<SkColorSpace> mColorSpace; - SkColorSpace::Gamut mColorGamut; SkColorType mColorType; VkSurfaceTransformFlagBitsKHR mTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; SkMatrix mPreTransform; @@ -113,7 +112,6 @@ public: // VulkanSurface object which is returned. VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode, sk_sp<SkColorSpace> surfaceColorSpace, - SkColorSpace::Gamut surfaceColorGamut, SkColorType surfaceColorType); // Destroy the VulkanSurface and all associated vulkan objects. diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 16a27598c56a..a9f651d38a06 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -78,24 +78,21 @@ sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater( return layerUpdater; } -void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y) { auto utf16 = asciiToUtf16(text); uint32_t length = strlen(text); - Paint glyphPaint(paint); - glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); + canvas->drawText(utf16.get(), length, // text buffer 0, length, // draw range 0, length, // context range - x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */); + x, y, minikin::Bidi::LTR, paint, nullptr, nullptr /* measured text */); } -void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path) { auto utf16 = asciiToUtf16(text); - Paint glyphPaint(paint); - glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); - canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint, + canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint, nullptr); } diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 6a1ca5a25361..e7124df72beb 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -278,10 +278,10 @@ public: static SkColor interpolateColor(float fraction, SkColor start, SkColor end); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path); static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str); diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index f0a5e9dff1b9..0795d13f441b 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -51,7 +51,7 @@ public: paint.setAntiAlias(true); paint.setColor(Color::Black); for (int i = 0; i < 5; i++) { - paint.setTextSize(10 + (frameNr % 20) + i * 20); + paint.getSkFont().setSize(10 + (frameNr % 20) + i * 20); TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2)); } diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index ec81f629ee45..2af955fbb711 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -50,7 +50,8 @@ public: pixels[4000 + 4 * i + 3] = 255; } buffer->unlock(); - sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB())); + sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, kRGBA_8888_SkColorType, + SkColorSpace::MakeSRGB())); sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap)); SkPoint center; diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 58c99800875b..ecaaf487e4f8 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -16,7 +16,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" - +#include "hwui/Paint.h" #include <SkGradientShader.h> class ListOfFadedTextAnimation; @@ -33,8 +33,8 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase { canvas.drawColor(Color::White, SkBlendMode::kSrcOver); int length = dp(100); canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer); - SkPaint textPaint; - textPaint.setTextSize(dp(20)); + Paint textPaint; + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30)); diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index 4111bd24847e..feb881f654f8 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -16,6 +16,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" +#include "hwui/Paint.h" #include <SkFont.h> #include <cstdio> @@ -83,14 +84,14 @@ class ListViewAnimation : public TestListViewSceneBase { roundRectPaint.setColor(Color::White); canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); - SkPaint textPaint; + Paint textPaint; textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500); - textPaint.setTextSize(dp(20)); + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); char buf[256]; snprintf(buf, sizeof(buf), "This card is #%d", cardId); TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25)); - textPaint.setTextSize(dp(15)); + textPaint.getSkFont().setSize(dp(15)); TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint, itemHeight, dp(45)); diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index aa537b4f329c..f6cff1c643a1 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "renderthread/RenderProxy.h" #include "utils/Color.h" +#include "hwui/Paint.h" class MagnifierAnimation; @@ -37,9 +38,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode( 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400); diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index 3befce4a395f..8630be87c09c 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -41,9 +41,9 @@ public: int top = bounds.fTop; mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255)); - mBluePaint.setTextSize(padding); + mBluePaint.getSkFont().setSize(padding); mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0)); - mGreenPaint.setTextSize(padding); + mGreenPaint.getSkFont().setSize(padding); // interleave drawText and drawRect with saveLayer ops for (int i = 0; i < regions; i++, top += smallRectHeight) { diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index a16b17849fc6..d30903679bce 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -15,6 +15,7 @@ */ #include "TestSceneBase.h" +#include "hwui/Paint.h" class TextAnimation; @@ -28,9 +29,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); for (int i = 0; i < 10; i++) { diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp index 286f5f194aed..229c7f392629 100644 --- a/libs/hwui/tests/common/scenes/TvApp.cpp +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -17,6 +17,7 @@ #include "SkBlendMode.h" #include "TestSceneBase.h" #include "tests/common/BitmapAllocationTestUtils.h" +#include "hwui/Paint.h" class TvApp; class TvAppNoRoundedCorner; @@ -116,13 +117,13 @@ private: [text, text2](RenderProperties& props, Canvas& canvas) { canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver); - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(24); + paint.getSkFont().setSize(24); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30); - paint.setTextSize(20); + paint.getSkFont().setSize(20); TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54); }); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 4415a593f6ee..d14116f7b555 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -45,6 +45,20 @@ android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType) { } } +SkColorType PixelFormatToColorType(android::PixelFormat format) { + switch (format) { + case PIXEL_FORMAT_RGBX_8888: return kRGB_888x_SkColorType; + case PIXEL_FORMAT_RGBA_8888: return kRGBA_8888_SkColorType; + case PIXEL_FORMAT_RGBA_FP16: return kRGBA_F16_SkColorType; + case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; + case PIXEL_FORMAT_RGBA_1010102: return kRGBA_1010102_SkColorType; + case PIXEL_FORMAT_RGBA_4444: return kARGB_4444_SkColorType; + default: + ALOGW("Unsupported PixelFormat: %d, return kUnknown_SkColorType by default", format); + return kUnknown_SkColorType; + } +} + sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { skcms_Matrix3x3 gamut; diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 388025207ed6..b67d10d4249c 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -112,6 +112,7 @@ static constexpr float EOCF(float srgb) { } android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); +ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format); ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); diff --git a/media/Android.bp b/media/Android.bp index 88ed9c6a05a9..0675a36f3e81 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -132,7 +132,6 @@ filegroup { "apex/java/android/media/Session2Command.java", "apex/java/android/media/Session2CommandGroup.java", "apex/java/android/media/Session2Link.java", - "apex/java/android/media/Session2Token.java", ], } diff --git a/media/apex/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java index 65b6f55a068a..45ea8261c6fb 100644 --- a/media/apex/java/android/media/MediaConstants.java +++ b/media/apex/java/android/media/MediaConstants.java @@ -24,7 +24,8 @@ class MediaConstants { static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME"; // Bundle key for Parcelable - static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK"; + static final String KEY_SESSION2_TOKEN = "android.media.key.SESSION2_TOKEN"; + static final String KEY_SESSION2_LINK = "android.media.key.SESSION2_LINK"; static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS"; static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE"; diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java index 887b4475a4d1..41721f3011b1 100644 --- a/media/apex/java/android/media/MediaController2.java +++ b/media/apex/java/android/media/MediaController2.java @@ -20,9 +20,11 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; @@ -260,7 +262,8 @@ public class MediaController2 implements AutoCloseable { // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { - Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); + Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN); + Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK); Session2CommandGroup allowedCommands = connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE); @@ -281,8 +284,7 @@ public class MediaController2 implements AutoCloseable { // Implementation for the local binder is no-op, // so can be used without worrying about deadlock. sessionBinder.linkToDeath(mDeathRecipient, 0); - mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, - mSessionToken.getPackageName(), sessionBinder); + mConnectedToken = token; } mCallbackExecutor.execute(() -> { mCallback.onConnected(MediaController2.this, allowedCommands); @@ -353,7 +355,7 @@ public class MediaController2 implements AutoCloseable { } private boolean requestConnectToSession() { - Session2Link sessionBinder = mSessionToken.getSessionLink(); + Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK); Bundle connectionRequest = createConnectionRequest(); try { sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest); @@ -366,7 +368,7 @@ public class MediaController2 implements AutoCloseable { private boolean requestConnectToService() { // Service. Needs to get fresh binder whenever connection is needed. - final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE); + final Intent intent = new Intent(SESSION_SERVICE_INTERFACE); intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName()); // Use bindService() instead of startForegroundService() to start session service for three diff --git a/media/apex/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java index fdd07fdd52e3..80c91cc79c78 100644 --- a/media/apex/java/android/media/MediaSession2.java +++ b/media/apex/java/android/media/MediaSession2.java @@ -20,10 +20,10 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; -import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,7 +34,6 @@ import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.Bundle; import android.os.Handler; -import android.os.Process; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.ArraySet; @@ -108,8 +107,10 @@ public class MediaSession2 implements AutoCloseable { mCallbackExecutor = callbackExecutor; mCallback = callback; mSessionStub = new Session2Link(this); - mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), - mSessionStub); + + Bundle extras = new Bundle(); + extras.putParcelable(KEY_SESSION2_LINK, mSessionStub); + mSessionToken = new Session2Token(context, id, extras); mSessionManager = (MediaSessionManager) mContext.getSystemService( Context.MEDIA_SESSION_SERVICE); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. @@ -141,6 +142,8 @@ public class MediaSession2 implements AutoCloseable { for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } + mSessionToken.destroy(); + mSessionManager.notifySession2Destroyed(mSessionToken); } catch (Exception e) { // Should not be here. } @@ -328,7 +331,7 @@ public class MediaSession2 implements AutoCloseable { // It's needed because we cannot call synchronous calls between // session/controller. Bundle connectionResult = new Bundle(); - connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); + connectionResult.putParcelable(KEY_SESSION2_TOKEN, mSessionToken); connectionResult.putParcelable(KEY_ALLOWED_COMMANDS, controllerInfo.mAllowedCommands); connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive()); diff --git a/media/apex/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java index 5bb746a7f9e3..f18cd317ef12 100644 --- a/media/apex/java/android/media/MediaSession2Service.java +++ b/media/apex/java/android/media/MediaSession2Service.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -45,10 +47,6 @@ import java.util.Map; * for consistent behavior across all devices. */ public abstract class MediaSession2Service extends Service { - /** - * The {@link Intent} that must be declared as handled by the service. - */ - public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -100,7 +98,7 @@ public abstract class MediaSession2Service extends Service { @Override @Nullable public IBinder onBind(@NonNull Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { + if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) { synchronized (mLock) { return mStub; } diff --git a/media/apex/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java index 238cc2b8ee7d..80494ad1b2a6 100644 --- a/media/apex/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -19,13 +19,17 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.media.session.MediaSessionManager; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.text.TextUtils; import android.util.Log; @@ -35,7 +39,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. + * Represents an ongoing MediaSession2 or a MediaSession2Service. * If it's representing a session service, it may not be ongoing. * <p> * This API is not generally intended for third party application developers. @@ -44,7 +48,7 @@ import java.util.Objects; * for consistent behavior across all devices. * <p> * This may be passed to apps by the session owner to allow them to create a - * {@link MediaController2} to communicate with the session. + * MediaController2 to communicate with the session. * <p> * It can be also obtained by {@link android.media.session.MediaSessionManager}. */ @@ -64,6 +68,13 @@ public final class Session2Token implements Parcelable { }; /** + * The {@link Intent} that must be declared for the session service. + * @hide + */ + @SystemApi + public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + + /** * @hide */ @Retention(RetentionPolicy.SOURCE) @@ -72,22 +83,26 @@ public final class Session2Token implements Parcelable { } /** - * Type for {@link MediaSession2}. + * Type for MediaSession2. */ public static final int TYPE_SESSION = 0; /** - * Type for {@link MediaSession2Service}. + * Type for MediaSession2Service. */ public static final int TYPE_SESSION_SERVICE = 1; + private final String mSessionId; + private final int mPid; private final int mUid; @TokenType private final int mType; private final String mPackageName; private final String mServiceName; - private final Session2Link mSessionLink; private final ComponentName mComponentName; + private final Bundle mExtras; + + private boolean mDestroyed = false; /** * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}. @@ -106,44 +121,67 @@ public final class Session2Token implements Parcelable { final PackageManager manager = context.getPackageManager(); final int uid = getUid(manager, serviceComponent.getPackageName()); - if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE, - serviceComponent)) { + if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) { Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service."); } + mSessionId = null; mComponentName = serviceComponent; mPackageName = serviceComponent.getPackageName(); mServiceName = serviceComponent.getClassName(); + mPid = -1; mUid = uid; mType = TYPE_SESSION_SERVICE; - mSessionLink = null; + mExtras = null; } - Session2Token(int uid, int type, String packageName, Session2Link sessionLink) { - mUid = uid; - mType = type; - mPackageName = packageName; + /** + * Constructor for the token with type {@link #TYPE_SESSION}. + * + * @param context The context. + * @param sessionId The ID of the session. Should be unique. + * @param extras The extras. + * @hide + */ + @SystemApi + public Session2Token(@NonNull Context context, @NonNull String sessionId, + @Nullable Bundle extras) { + if (sessionId == null) { + throw new IllegalArgumentException("sessionId shouldn't be null"); + } + if (context == null) { + throw new IllegalArgumentException("context shouldn't be null"); + } + mSessionId = sessionId; + mPid = Process.myPid(); + mUid = Process.myUid(); + mType = TYPE_SESSION; + mPackageName = context.getPackageName(); + mExtras = extras; mServiceName = null; mComponentName = null; - mSessionLink = sessionLink; } Session2Token(Parcel in) { + mSessionId = in.readString(); + mPid = in.readInt(); mUid = in.readInt(); mType = in.readInt(); mPackageName = in.readString(); mServiceName = in.readString(); - mSessionLink = in.readParcelable(null); mComponentName = ComponentName.unflattenFromString(in.readString()); + mExtras = in.readParcelable(null); } @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mSessionId); + dest.writeInt(mPid); dest.writeInt(mUid); dest.writeInt(mType); dest.writeString(mPackageName); dest.writeString(mServiceName); - dest.writeParcelable(mSessionLink, flags); dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString()); + dest.writeParcelable(mExtras, flags); } @Override @@ -153,7 +191,7 @@ public final class Session2Token implements Parcelable { @Override public int hashCode() { - return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink); + return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName); } @Override @@ -162,17 +200,27 @@ public final class Session2Token implements Parcelable { return false; } Session2Token other = (Session2Token) obj; - return mUid == other.mUid - && TextUtils.equals(mPackageName, other.mPackageName) - && TextUtils.equals(mServiceName, other.mServiceName) + return TextUtils.equals(mSessionId, other.mSessionId) + && mPid == other.mPid + && mUid == other.mUid && mType == other.mType - && Objects.equals(mSessionLink, other.mSessionLink); + && TextUtils.equals(mPackageName, other.mPackageName) + && TextUtils.equals(mServiceName, other.mServiceName); } @Override public String toString() { return "Session2Token {pkg=" + mPackageName + " type=" + mType - + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}"; + + " service=" + mServiceName + "}"; + } + + /** + * @return pid of the session + * @hide + */ + @SystemApi + public int getPid() { + return mPid; } /** @@ -207,8 +255,36 @@ public final class Session2Token implements Parcelable { return mType; } - Session2Link getSessionLink() { - return mSessionLink; + /** + * @return extras + * @hide + */ + @SystemApi + @NonNull + public Bundle getExtras() { + return mExtras == null ? new Bundle() : new Bundle(mExtras); + } + + /** + * Destroys this session token. After this method is called, + * {@link MediaSessionManager#notifySession2Created(Session2Token)} should not be called + * with this token. + * + * @see MediaSessionManager#notifySession2Created(Session2Token) + * @hide + */ + @SystemApi + public void destroy() { + mDestroyed = true; + } + + /** + * @return whether this token is destroyed + * @hide + */ + @SystemApi + public boolean isDestroyed() { + return mDestroyed; } private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface, diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index ed162504c553..fa6e03430315 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -37,6 +37,7 @@ interface ISessionManager { SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag, int userId); void notifySession2Created(in Session2Token sessionToken); + void notifySession2Destroyed(in Session2Token sessionToken); List<ControllerLink> getSessions(in ComponentName compName, int userId); List<Session2Token> getSession2Tokens(int userId); void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent, diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index c64c452be3ef..cae4d1749287 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -26,7 +26,6 @@ import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; import android.media.IRemoteVolumeController; -import android.media.MediaSession2; import android.media.Session2Token; import android.os.Handler; import android.os.IBinder; @@ -115,11 +114,11 @@ public final class MediaSessionManager { } /** - * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is * created. * <p> * Do not use this API directly, but create a new instance through the - * {@link MediaSession2.Builder} instead. + * MediaSession2.Builder instead. * * @param token newly created session2 token */ @@ -130,6 +129,9 @@ public final class MediaSessionManager { if (token.getType() != Session2Token.TYPE_SESSION) { throw new IllegalArgumentException("token's type should be TYPE_SESSION"); } + if (token.isDestroyed()) { + throw new IllegalArgumentException("token is already destroyed"); + } try { mService.notifySession2Created(token); } catch (RemoteException e) { @@ -138,6 +140,31 @@ public final class MediaSessionManager { } /** + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is + * destroyed. + * <p> + * Do not use this API directly, but close a session with MediaSession2#close() instead. + * + * @param token destroyed session2 token + */ + public void notifySession2Destroyed(@NonNull Session2Token token) { + if (token == null) { + throw new IllegalArgumentException("token shouldn't be null"); + } + if (token.getType() != Session2Token.TYPE_SESSION) { + throw new IllegalArgumentException("token's type should be TYPE_SESSION"); + } + if (!token.isDestroyed()) { + throw new IllegalArgumentException("token should have been destroyed"); + } + try { + mService.notifySession2Destroyed(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Get a list of controllers for all ongoing sessions. The controllers will * be provided in priority order with the most important controller at index * 0. @@ -192,7 +219,7 @@ public final class MediaSessionManager { * current user. * <p> * Although this API can be used without any restriction, each session owners can accept or - * reject your uses of {@link MediaSession2}. + * reject your uses of MediaSession2. * * @return A list of {@link Session2Token}. */ diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h index 0490e650a7a4..e31ce195214f 100644 --- a/native/webview/plat_support/draw_fn.h +++ b/native/webview/plat_support/draw_fn.h @@ -20,7 +20,8 @@ extern "C" { // android to chromium are versioned. // // 1 is Android Q. This matches kAwDrawGLInfoVersion version 3. -static const int kAwDrawFnVersion = 1; +// 2 Adds transfer_function_* and color_space_toXYZD50 to AwDrawFn_DrawGLParams. +static const int kAwDrawFnVersion = 2; struct AwDrawFn_OnSyncParams { int version; @@ -64,6 +65,16 @@ struct AwDrawFn_DrawGLParams { // Input: current transformation matrix in surface pixels. // Uses the column-based OpenGL matrix format. float transform[16]; + + // Input: Color space parameters. + float transfer_function_g; + float transfer_function_a; + float transfer_function_b; + float transfer_function_c; + float transfer_function_d; + float transfer_function_e; + float transfer_function_f; + float color_space_toXYZD50[9]; }; struct AwDrawFn_InitVkParams { diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp index b97bbc311624..afe103a25043 100644 --- a/native/webview/plat_support/draw_functor.cpp +++ b/native/webview/plat_support/draw_functor.cpp @@ -55,6 +55,8 @@ void onDestroyed(int functor, void* data) { void draw_gl(int functor, void* data, const uirenderer::DrawGlInfo& draw_gl_params) { + float gabcdef[7]; + draw_gl_params.color_space_ptr->transferFn(gabcdef); AwDrawFn_DrawGLParams params = { .version = kAwDrawFnVersion, .clip_left = draw_gl_params.clipLeft, @@ -64,12 +66,24 @@ void draw_gl(int functor, void* data, .width = draw_gl_params.width, .height = draw_gl_params.height, .is_layer = draw_gl_params.isLayer, + .transfer_function_g = gabcdef[0], + .transfer_function_a = gabcdef[1], + .transfer_function_b = gabcdef[2], + .transfer_function_c = gabcdef[3], + .transfer_function_d = gabcdef[4], + .transfer_function_e = gabcdef[5], + .transfer_function_f = gabcdef[6], }; COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_gl_params.transform), mismatched_transform_matrix_sizes); for (int i = 0; i < NELEM(params.transform); ++i) { params.transform[i] = draw_gl_params.transform[i]; } + COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3), + gamut_transform_size_mismatch); + draw_gl_params.color_space_ptr->toXYZD50( + reinterpret_cast<skcms_Matrix3x3*>(¶ms.color_space_toXYZD50)); + SupportData* support = static_cast<SupportData*>(data); support->callbacks.draw_gl(functor, support->data, ¶ms); } diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp index 9b6ad38545b4..9064ebe80da1 100644 --- a/packages/CarSystemUI/Android.bp +++ b/packages/CarSystemUI/Android.bp @@ -81,5 +81,5 @@ android_app { "com.android.keyguard", ], - annotation_processors: ["dagger2-compiler-2.19"], + plugins: ["dagger2-compiler-2.19"], } diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml index 052566d67c1b..e591ea90c112 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml @@ -65,8 +65,9 @@ <com.android.systemui.statusbar.car.CarFacetButton android:id="@+id/music_nav" style="@style/NavigationBarButton" + systemui:categories="android.intent.category.APP_MUSIC" systemui:icon="@drawable/car_ic_music" - systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end" + systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end" systemui:packages="com.android.car.media" systemui:selectedIcon="@drawable/car_ic_music_selected" systemui:useMoreIcon="false" diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index dbddf71d342c..b37c5e69df76 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -114,9 +114,16 @@ public class CarStatusBar extends StatusBar implements new DeviceProvisionedController.DeviceProvisionedListener() { @Override public void onDeviceProvisionedChanged() { - mDeviceIsProvisioned = - mDeviceProvisionedController.isDeviceProvisioned(); - restartNavBars(); + mHandler.post(() -> { + // on initial boot we are getting a call even though the value + // is the same so we are confirming the reset is needed + boolean deviceProvisioned = + mDeviceProvisionedController.isDeviceProvisioned(); + if (mDeviceIsProvisioned != deviceProvisioned) { + mDeviceIsProvisioned = deviceProvisioned; + restartNavBars(); + } + }); } }); } diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index 0d528e7078f8..5acf4fbaa5cb 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -202,7 +202,7 @@ public class SmartActionsHelper { } TextClassifierEvent textClassifierEvent = createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId) - .setEntityType(ConversationAction.TYPE_TEXT_REPLY) + .setEntityTypes(ConversationAction.TYPE_TEXT_REPLY) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } @@ -225,7 +225,7 @@ public class SmartActionsHelper { } TextClassifierEvent textClassifierEvent = createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId) - .setEntityType(actionType) + .setEntityTypes(actionType) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } @@ -291,7 +291,7 @@ public class SmartActionsHelper { Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); if (messages == null || messages.length == 0) { return Arrays.asList(new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_REMOTE) + ConversationActions.Message.PERSON_USER_OTHERS) .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT)) .build()); } @@ -310,7 +310,7 @@ public class SmartActionsHelper { break; } Person author = localUser != null && localUser.equals(senderPerson) - ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson; + ? ConversationActions.Message.PERSON_USER_SELF : senderPerson; extractMessages.push(new ConversationActions.Message.Builder(author) .setText(message.getText()) .setReferenceTime( diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java index 707349b0fd15..7f8127aa43a8 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -154,7 +154,7 @@ public class SmartActionHelperTest { ConversationActions.Message secondMessage = messages.get(0); MessageSubject.assertThat(secondMessage).hasText("secondMessage"); MessageSubject.assertThat(secondMessage) - .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL); + .hasPerson(ConversationActions.Message.PERSON_USER_SELF); MessageSubject.assertThat(secondMessage) .hasReferenceTime(createZonedDateTimeFromMsUtc(2000)); diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index f21809fdbc1c..4ae044dec20b 100644 --- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -103,9 +103,7 @@ public class IpClientTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); - when(mContext.getSystemServiceName(ConnectivityManager.class)) - .thenReturn(Context.CONNECTIVITY_SERVICE); - when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm); + when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 74aaf3c26aba..93f6a94dcf49 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -47,6 +47,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import java.util.List; +import java.util.Set; /** * Utility class to host methods usable in adding a restricted padlock icon and showing admin @@ -325,7 +326,8 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { if (admin == null) { return null; } - if (dpm.getCrossProfileCalendarPackages().isEmpty()) { + final Set<String> packages = dpm.getCrossProfileCalendarPackages(); + if (packages != null && packages.isEmpty()) { return admin; } return null; diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index 7357fe63d9b0..42afb69a27f4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -16,6 +16,7 @@ package com.android.settingslib.applications; +import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; @@ -123,4 +124,12 @@ public class AppUtils { return null; } + /** + * Returns a boolean indicating whether the given package is a hidden system module + */ + public static boolean isHiddenSystemModule(Context context, String packageName) { + return ApplicationsState.getInstance((Application) context.getApplicationContext()) + .isHiddenModule(packageName); + } + } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index a936df2bf2eb..c9fbc7ba9f05 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -29,6 +29,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.IPackageStatsObserver; +import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageStats; @@ -71,6 +72,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -95,9 +97,14 @@ public class ApplicationsState { static ApplicationsState sInstance; public static ApplicationsState getInstance(Application app) { + return getInstance(app, AppGlobals.getPackageManager()); + } + + @VisibleForTesting + static ApplicationsState getInstance(Application app, IPackageManager iPackageManager) { synchronized (sLock) { if (sInstance == null) { - sInstance = new ApplicationsState(app); + sInstance = new ApplicationsState(app, iPackageManager); } return sInstance; } @@ -132,6 +139,7 @@ public class ApplicationsState { String mCurComputingSizePkg; int mCurComputingSizeUserId; boolean mSessionsChanged; + final HashSet<String> mHiddenModules = new HashSet<>(); // Temporary for dispatching session callbacks. Only touched by main thread. final ArrayList<WeakReference<Session>> mActiveSessions = new ArrayList<>(); @@ -172,11 +180,11 @@ public class ApplicationsState { FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS | FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER; - private ApplicationsState(Application app) { + private ApplicationsState(Application app, IPackageManager iPackageManager) { mContext = app; mPm = mContext.getPackageManager(); mDrawableFactory = IconDrawableFactory.newInstance(mContext); - mIpm = AppGlobals.getPackageManager(); + mIpm = iPackageManager; mUm = mContext.getSystemService(UserManager.class); mStats = mContext.getSystemService(StorageStatsManager.class); for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) { @@ -194,6 +202,13 @@ public class ApplicationsState { mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; + final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */); + for (ModuleInfo info : moduleInfos) { + if (info.isHidden()) { + mHiddenModules.add(info.getPackageName()); + } + } + /** * This is a trick to prevent the foreground thread from being delayed. * The problem is that Dalvik monitors are initially spin locks, to keep @@ -283,6 +298,10 @@ public class ApplicationsState { } mHaveDisabledApps = true; } + if (isHiddenModule(info.packageName)) { + mApplications.remove(i--); + continue; + } if (!mHaveInstantApps && AppUtils.isInstant(info)) { mHaveInstantApps = true; } @@ -314,10 +333,15 @@ public class ApplicationsState { public boolean haveDisabledApps() { return mHaveDisabledApps; } + public boolean haveInstantApps() { return mHaveInstantApps; } + boolean isHiddenModule(String packageName) { + return mHiddenModules.contains(packageName); + } + void doPauseIfNeededLocked() { if (!mResumed) { return; diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java index d9578014e846..305a1ff97e71 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java @@ -22,7 +22,6 @@ import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; -import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; import android.net.NetworkPolicy; @@ -35,14 +34,14 @@ import android.os.ServiceManager; import android.text.format.DateUtils; import android.util.Pair; +import androidx.annotation.VisibleForTesting; +import androidx.loader.content.AsyncTaskLoader; + import com.android.settingslib.NetworkPolicyEditor; import java.time.ZonedDateTime; import java.util.Iterator; -import androidx.annotation.VisibleForTesting; -import androidx.loader.content.AsyncTaskLoader; - /** * Loader for network data usage history. It returns a list of usage data per billing cycle. */ @@ -121,8 +120,7 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { long cycleEnd = historyEnd; while (cycleEnd > historyStart) { - final long cycleStart = Math.max( - historyStart, cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4)); + final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); recordUsage(cycleStart, cycleEnd); cycleEnd = cycleStart; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index ccec175aefad..a098ecc17b3d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -18,7 +18,9 @@ package com.android.settingslib.applications; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; @@ -33,12 +35,16 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.UserHandle; +import android.text.TextUtils; import android.util.IconDrawableFactory; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -46,11 +52,11 @@ import com.android.settingslib.applications.ApplicationsState.Callbacks; import com.android.settingslib.applications.ApplicationsState.Session; import com.android.settingslib.testutils.shadow.ShadowUserManager; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -78,6 +84,8 @@ public class ApplicationsStateRoboTest { /** Class under test */ private ApplicationsState mApplicationsState; + private Session mSession; + @Mock private Callbacks mCallbacks; @@ -85,6 +93,8 @@ public class ApplicationsStateRoboTest { private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor; @Mock private StorageStatsManager mStorageStatsManager; + @Mock + private IPackageManager mPackageManagerService; @Implements(value = IconDrawableFactory.class) public static class ShadowIconDrawableFactory { @@ -99,6 +109,11 @@ public class ApplicationsStateRoboTest { public static class ShadowPackageManager extends org.robolectric.shadows.ShadowApplicationPackageManager { + // test installed modules, 2 regular, 2 hidden + private final String[] mModuleNames = { + "test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"}; + private final List<ModuleInfo> mInstalledModules = new ArrayList<>(); + @Implementation protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) { ResolveInfo resolveInfo = new ResolveInfo(); @@ -109,6 +124,16 @@ public class ApplicationsStateRoboTest { return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo"); } + @Implementation + public List<ModuleInfo> getInstalledModules(int flags) { + if (mInstalledModules.isEmpty()) { + for (String moduleName : mModuleNames) { + mInstalledModules.add(createModuleInfo(moduleName)); + } + } + return mInstalledModules; + } + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId) { List<ResolveInfo> resolveInfos = new ArrayList<>(); @@ -121,6 +146,15 @@ public class ApplicationsStateRoboTest { resolveInfos.add(resolveInfo); return resolveInfos; } + + private ModuleInfo createModuleInfo(String packageName) { + final ModuleInfo info = new ModuleInfo(); + info.setName(packageName); + info.setPackageName(packageName); + // will treat any app with package name that contains "hidden" as hidden module + info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden")); + return info; + } } @Before @@ -136,12 +170,28 @@ public class ApplicationsStateRoboTest { storageStats.codeBytes = 10; storageStats.dataBytes = 20; storageStats.cacheBytes = 30; - when(mStorageStatsManager.queryStatsForPackage(ArgumentMatchers.any(UUID.class), - anyString(), ArgumentMatchers.any(UserHandle.class))).thenReturn(storageStats); + when(mStorageStatsManager.queryStatsForPackage(any(UUID.class), + anyString(), any(UserHandle.class))).thenReturn(storageStats); + + // Set up 3 installed apps, in which 1 is hidden module + final List<ApplicationInfo> infos = new ArrayList<>(); + infos.add(createApplicationInfo("test.package.1")); + infos.add(createApplicationInfo("test.hidden.module.2")); + infos.add(createApplicationInfo("test.package.3")); + when(mPackageManagerService.getInstalledApplications( + anyInt() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos)); ApplicationsState.sInstance = null; - mApplicationsState = ApplicationsState.getInstance(RuntimeEnvironment.application); + mApplicationsState = + ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService); mApplicationsState.clearEntries(); + + mSession = mApplicationsState.newSession(mCallbacks); + } + + @After + public void tearDown() { + mSession.onDestroy(); } private ApplicationInfo createApplicationInfo(String packageName) { @@ -187,12 +237,11 @@ public class ApplicationsStateRoboTest { @Test public void testDefaultSessionLoadsAll() { - Session session = mApplicationsState.newSession(mCallbacks); - session.onResume(); + mSession.onResume(); addApp(HOME_PACKAGE_NAME, 1); addApp(LAUNCHABLE_PACKAGE_NAME, 2); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -211,17 +260,15 @@ public class ApplicationsStateRoboTest { AppEntry launchableEntry = findAppEntry(appEntries, 2); assertThat(launchableEntry.hasLauncherEntry).isTrue(); assertThat(launchableEntry.launcherEntryEnabled).isTrue(); - session.onDestroy(); } @Test public void testCustomSessionLoadsIconsOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS); + mSession.onResume(); addApp(LAUNCHABLE_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -232,17 +279,15 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.icon).isNotNull(); assertThat(launchableEntry.size).isEqualTo(-1); assertThat(launchableEntry.hasLauncherEntry).isFalse(); - session.onDestroy(); } @Test public void testCustomSessionLoadsSizesOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES); + mSession.onResume(); addApp(LAUNCHABLE_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -253,17 +298,15 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.icon).isNull(); assertThat(launchableEntry.hasLauncherEntry).isFalse(); assertThat(launchableEntry.size).isGreaterThan(0L); - session.onDestroy(); } @Test public void testCustomSessionLoadsHomeOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP); + mSession.onResume(); addApp(HOME_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -275,17 +318,15 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.hasLauncherEntry).isFalse(); assertThat(launchableEntry.size).isEqualTo(-1); assertThat(launchableEntry.isHomeApp).isTrue(); - session.onDestroy(); } @Test public void testCustomSessionLoadsLeanbackOnly() { - Session session = mApplicationsState.newSession(mCallbacks); - session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER); - session.onResume(); + mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER); + mSession.onResume(); addApp(LAUNCHABLE_PACKAGE_NAME, 1); - session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -298,6 +339,16 @@ public class ApplicationsStateRoboTest { assertThat(launchableEntry.isHomeApp).isFalse(); assertThat(launchableEntry.hasLauncherEntry).isTrue(); assertThat(launchableEntry.launcherEntryEnabled).isTrue(); - session.onDestroy(); } + + @Test + public void onResume_shouldNotIncludeSystemHiddenModule() { + mSession.onResume(); + + final List<ApplicationInfo> mApplications = mApplicationsState.mApplications; + assertThat(mApplications).hasSize(2); + assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1"); + assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3"); + } + } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java index 2d8ea125a97e..b8a143a376fd 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java @@ -130,7 +130,8 @@ public class NetworkCycleDataLoaderTest { .thenReturn(networkHistory); final long now = System.currentTimeMillis(); final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4); - when(networkHistory.getStart()).thenReturn(fourWeeksAgo); + final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2); + when(networkHistory.getStart()).thenReturn(twoDaysAgo); when(networkHistory.getEnd()).thenReturn(now); mLoader.loadFourWeeksData(); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 850a3c2dea9b..aff6f0452533 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -698,6 +698,9 @@ class SettingsProtoDumpUtil { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES); dumpSetting(s, p, + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, + GlobalSettingsProto.Gpu.ANGLE_WHITELIST); + dumpSetting(s, p, Settings.Global.GPU_DEBUG_LAYER_APP, GlobalSettingsProto.Gpu.DEBUG_LAYER_APP); dumpSetting(s, p, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 8be67d9a7a51..e0d178fb9a1e 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -57,6 +57,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "dagger2-2.19", @@ -73,7 +74,7 @@ android_library { "com.android.keyguard", ], - annotation_processors: ["dagger2-compiler-2.19"], + plugins: ["dagger2-compiler-2.19"], } android_library { @@ -108,6 +109,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "androidx.dynamicanimation_dynamicanimation", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", @@ -127,7 +129,7 @@ android_library { "--extra-packages", "com.android.keyguard:com.android.systemui", ], - annotation_processors: ["dagger2-compiler-2.19"], + plugins: ["dagger2-compiler-2.19"], } android_app { diff --git a/packages/SystemUI/docs/physics-animation-layout-config-methods.png b/packages/SystemUI/docs/physics-animation-layout-config-methods.png Binary files differnew file mode 100644 index 000000000000..c3a45e294e79 --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout-config-methods.png diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png Binary files differnew file mode 100644 index 000000000000..e77c676bc13f --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout-control-methods.png diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md new file mode 100644 index 000000000000..a67b5e873b2e --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-layout.md @@ -0,0 +1,56 @@ +# Physics Animation Layout + +## Overview +**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively. + +An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen. + +## PhysicsAnimationController +PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events. + +### Configuration Methods + +The controller must override the following methods: + +```Set<ViewProperty> getAnimatedProperties()``` +Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations. + +```int getNextAnimationInChain(ViewProperty property, int index)``` +If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind. + +```float getOffsetForChainedPropertyAnimation(ViewProperty property)``` +Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc. + +```SpringForce getSpringForce(ViewProperty property)``` +Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed. + +### Animation Control Methods + +Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views. + +In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation. + +For example, moving the first child view to *(100, 200)*: + +``` +animateValueForChildAtIndex(TRANSLATION_X, 0, 100); +animateValueForChildAtIndex(TRANSLATION_Y, 0, 200); +``` + +This would use the physics animations constructed by the layout to spring the view to *(100, 200)*. + +If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```. + +## PhysicsAnimationLayout +The layout itself is a FrameLayout descendant with a few extra methods: + +```setController(PhysicsAnimationController controller)``` +Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods. + +```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)``` +Sets an end listener that is called when all animations on the given property have ended. + +```setMaxRenderedChildren(int max)``` +Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**. + +It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file diff --git a/packages/SystemUI/docs/physics-animation-testing.md b/packages/SystemUI/docs/physics-animation-testing.md new file mode 100644 index 000000000000..47354d45fa33 --- /dev/null +++ b/packages/SystemUI/docs/physics-animation-testing.md @@ -0,0 +1,11 @@ +# Physics Animation Testing +Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic. + +For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions. + +## Waiting for Animations to End +Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations. + +To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important. + +The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete.
\ No newline at end of file diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index 0b1dab1c3bca..fc84332151ec 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -20,14 +20,14 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import java.util.ArrayList; - import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.annotations.DependsOn; import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; + +import java.util.ArrayList; @ProvidesInterface(action = NotificationMenuRowPlugin.ACTION, version = NotificationMenuRowPlugin.VERSION) @@ -149,6 +149,12 @@ public interface NotificationMenuRowPlugin extends Plugin { public boolean canBeDismissed(); /** + * Informs the menu whether dismiss gestures are left-to-right or right-to-left. + */ + default void setDismissRtl(boolean dismissRtl) { + } + + /** * Determines whether the menu should remain open given its current state, or snap closed. * @return true if the menu should remain open, false otherwise. */ diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml index 204408cda81f..13186fc6437c 100644 --- a/packages/SystemUI/res/layout/bubble_view.xml +++ b/packages/SystemUI/res/layout/bubble_view.xml @@ -22,8 +22,8 @@ <com.android.systemui.bubbles.BadgedImageView android:id="@+id/bubble_image" - android:layout_width="@dimen/bubble_size" - android:layout_height="@dimen/bubble_size" + android:layout_width="@dimen/individual_bubble_size" + android:layout_height="@dimen/individual_bubble_size" android:padding="@dimen/bubble_view_padding" android:clipToPadding="false"/> diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml new file mode 100644 index 000000000000..e6f2376ae76b --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.android.systemui.globalactions.GlobalActionsGridLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="bottom|center" + android:clipChildren="false" +> + + <LinearLayout + android:layout_height="290dp" + android:layout_width="412dp" + android:gravity="bottom" + android:padding="0dp" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" + > + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingTop="@dimen/global_actions_grid_top_padding" + android:paddingLeft="@dimen/global_actions_grid_left_padding" + android:paddingBottom="@dimen/global_actions_grid_bottom_padding" + android:paddingRight="@dimen/global_actions_grid_right_padding" + android:orientation="vertical" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + + <Space android:layout_width="match_parent" android:layout_height="2dp" + android:layout_weight="1" /> + + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="right" + android:orientation="horizontal" + android:layoutDirection="rtl" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_left_padding" + android:paddingRight="@dimen/global_actions_grid_right_padding" + android:paddingTop="@dimen/global_actions_grid_top_padding" + android:paddingBottom="@dimen/global_actions_grid_bottom_padding" + android:background="?android:attr/colorBackgroundFloating" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:visibility="gone" + android:gravity="bottom" + android:orientation="vertical" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsGridLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml new file mode 100644 index 000000000000..0c11cd977256 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="72dp" + android:layout_height="72dp" + android:gravity="center" + android:orientation="vertical" + android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_side_margin" + android:paddingEnd="4dip" + android:paddingStart="4dip"> + + <ImageView + android:id="@*android:id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:scaleType="center" + android:alpha="?android:attr/primaryContentAlpha" + /> + + <TextView + android:id="@*android:id/message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:paddingTop="10dp" + android:gravity="center" + android:textSize="12sp" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + <TextView + android:id="@*android:id/status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:gravity="center" + android:textColor="?android:attr/textColorTertiary" + android:textAppearance="?android:attr/textAppearanceSmall" + /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_footer_carrier.xml b/packages/SystemUI/res/layout/qs_footer_carrier.xml new file mode 100644 index 000000000000..bd492b022e36 --- /dev/null +++ b/packages/SystemUI/res/layout/qs_footer_carrier.xml @@ -0,0 +1,49 @@ +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/linear_footer_carrier" + android:layout_width="0dp" + android:layout_height="match_parent" + android:orientation="horizontal" + android:layout_weight="1" + android:gravity="center_vertical|start" + android:background="@android:color/transparent" + android:clickable="false" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingStart="16dp" > + + <include + layout="@layout/mobile_signal_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:visibility="gone" /> + + <view class="com.android.systemui.qs.QSFooterImpl$QSCarrierText" + android:id="@+id/qs_carrier_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="marquee" + android:textAppearance="@style/TextAppearance.QS.CarrierInfo" + android:textColor="?android:attr/textColorPrimary" + android:textDirection="locale" + android:singleLine="true" /> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 890bf5d8ac45..abf9e056ed22 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -42,30 +42,31 @@ android:gravity="end" > <LinearLayout + android:id="@+id/qs_mobile" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical|start" - android:paddingStart="16dp"> + android:orientation="horizontal" + android:layout_marginEnd="32dp"> <include - layout="@layout/mobile_signal_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="8dp" + layout="@layout/qs_footer_carrier" + android:id="@+id/carrier1" /> + + <View + android:id="@+id/qs_carrier_divider" + android:layout_width="2dp" + android:layout_height="match_parent" + android:layout_marginTop="15dp" + android:layout_marginBottom="15dp" + android:background="?android:attr/dividerVertical" android:visibility="gone" /> - <com.android.keyguard.CarrierText - android:id="@+id/qs_carrier_text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginEnd="32dp" - android:ellipsize="marquee" - android:textAppearance="@style/TextAppearance.QS.CarrierInfo" - android:textColor="?android:attr/textColorPrimary" - android:textDirection="locale" - android:singleLine="true" /> + <include + layout="@layout/qs_footer_carrier" + android:id="@+id/carrier2" + android:visibility="gone"/> </LinearLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ab0bbe10c37c..a14259eca45e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -834,6 +834,18 @@ <dimen name="global_actions_panel_width">120dp</dimen> + <dimen name="global_actions_grid_container_bottom_margin">16dp</dimen> + + <dimen name="global_actions_grid_side_margin">4dp</dimen> + <dimen name="global_actions_grid_separated_panel_width">104dp</dimen> + <dimen name="global_actions_grid_top_padding">8dp</dimen> + <dimen name="global_actions_grid_bottom_padding">8dp</dimen> + <dimen name="global_actions_grid_left_padding">4dp</dimen> + <dimen name="global_actions_grid_right_padding">4dp</dimen> + + <dimen name="global_actions_grid_item_side_margin">12dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">8dp</dimen> + <dimen name="global_actions_top_padding">120dp</dimen> <dimen name="global_actions_padding">12dp</dimen> @@ -982,8 +994,8 @@ <dimen name="bubble_view_padding">0dp</dimen> <!-- Padding between bubbles when displayed in expanded state --> <dimen name="bubble_padding">8dp</dimen> - <!-- Size of the collapsed bubble --> - <dimen name="bubble_size">56dp</dimen> + <!-- Size of individual bubbles. --> + <dimen name="individual_bubble_size">56dp</dimen> <!-- How much to inset the icon in the circle --> <dimen name="bubble_icon_inset">16dp</dimen> <!-- Padding around the view displayed when the bubble is expanded --> @@ -1000,10 +1012,20 @@ <dimen name="bubble_expanded_header_height">48dp</dimen> <!-- Left and right padding applied to the header. --> <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen> + <!-- How far, horizontally, to animate the expanded view over when animating in/out. --> + <dimen name="bubble_expanded_animate_x_distance">100dp</dimen> + <!-- How far, vertically, to animate the expanded view over when animating in/out. --> + <dimen name="bubble_expanded_animate_y_distance">500dp</dimen> <!-- Max width of the message bubble--> <dimen name="bubble_message_max_width">144dp</dimen> <!-- Min width of the message bubble --> <dimen name="bubble_message_min_width">32dp</dimen> <!-- Interior padding of the message bubble --> <dimen name="bubble_message_padding">4dp</dimen> + <!-- Offset between bubbles in their stacked position. --> + <dimen name="bubble_stack_offset">5dp</dimen> + <!-- How far offscreen the bubble stack rests. --> + <dimen name="bubble_stack_offscreen">5dp</dimen> + <!-- How far down the screen the stack starts. --> + <dimen name="bubble_stack_starting_offset_y">100dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index bd34beac7fd6..6a6845742788 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -68,6 +68,7 @@ <item type="id" name="panel_alpha_animator_tag"/> <item type="id" name="panel_alpha_animator_start_tag"/> <item type="id" name="panel_alpha_animator_end_tag"/> + <item type="id" name="cross_fade_layer_type_changed_tag"/> <!-- Whether the icon is from a notification for which targetSdk < L --> <item type="id" name="icon_is_pre_L"/> @@ -115,6 +116,14 @@ <item type="id" name="aod_mask_transition_progress_end_tag" /> <item type="id" name="aod_mask_transition_progress_start_tag" /> + <!-- For saving DynamicAnimation physics animations as view tags. --> + <item type="id" name="translation_x_dynamicanimation_tag"/> + <item type="id" name="translation_y_dynamicanimation_tag"/> + <item type="id" name="translation_z_dynamicanimation_tag"/> + <item type="id" name="alpha_dynamicanimation_tag"/> + <item type="id" name="scale_x_dynamicanimation_tag"/> + <item type="id" name="scale_y_dynamicanimation_tag"/> + <!-- Global Actions Menu --> <item type="id" name="global_actions_view" /> </resources> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index fd7a10500f36..e8fabf5a07f1 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -21,4 +21,10 @@ 0) as we can allow the carrier text to stretch as far as needed in the QS footer. --> <integer name="qs_footer_actions_width">-2</integer> <integer name="qs_footer_actions_weight">0</integer> + + <!-- Maximum number of bubbles to render and animate at one time. While the animations used are + lightweight translation animations, this number can be reduced on lower end devices if any + performance issues arise. --> + <integer name="bubbles_max_rendered">5</integer> + </resources>
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java index b7d51978fab2..adcb7a125e80 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java @@ -17,29 +17,14 @@ package com.android.keyguard; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.TypedArray; -import android.net.ConnectivityManager; -import android.net.wifi.WifiManager; -import android.telephony.ServiceState; -import android.telephony.SubscriptionInfo; -import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.method.SingleLineTransformationMethod; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.widget.TextView; -import com.android.internal.telephony.IccCardConstants; -import com.android.internal.telephony.IccCardConstants.State; -import com.android.internal.telephony.TelephonyIntents; -import com.android.settingslib.WirelessUtils; - -import java.util.List; import java.util.Locale; -import java.util.Objects; public class CarrierText extends TextView { private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -47,77 +32,30 @@ public class CarrierText extends TextView { private static CharSequence mSeparator; - private final boolean mIsEmergencyCallCapable; - - private boolean mTelephonyCapable; - private boolean mShowMissingSim; private boolean mShowAirplaneMode; + private boolean mShouldMarquee; - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - - private WifiManager mWifiManager; - - private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()]; - - private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onRefreshCarrierInfo() { - if (DEBUG) Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " - + Boolean.toString(mTelephonyCapable)); - updateCarrierText(); - } - - public void onFinishedGoingToSleep(int why) { - setSelected(false); - }; - - public void onStartedWakingUp() { - setSelected(true); - }; + private CarrierTextController mCarrierTextController; - @Override - public void onTelephonyCapable(boolean capable) { - if (DEBUG) Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " - + Boolean.toString(capable)); - mTelephonyCapable = capable; - updateCarrierText(); - } - - public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { - if (slotId < 0) { - Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId - + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); - return; - } + private CarrierTextController.CarrierTextCallback mCarrierTextCallback = + new CarrierTextController.CarrierTextCallback() { + @Override + public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + setText(info.carrierText); + } - if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState)); - if (getStatusForIccState(simState) == StatusMode.SimIoError) { - mSimErrorState[slotId] = true; - updateCarrierText(); - } else if (mSimErrorState[slotId]) { - mSimErrorState[slotId] = false; - updateCarrierText(); - } - } - }; + @Override + public void startedGoingToSleep() { + setSelected(false); + } - /** - * The status of this lock screen. Primarily used for widgets on LockScreen. - */ - private static enum StatusMode { - Normal, // Normal case (sim card present, it's not locked) - NetworkLocked, // SIM card is 'network locked'. - SimMissing, // SIM card is missing. - SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access - SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times - SimLocked, // SIM card is currently locked - SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure - SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. - SimIoError, // SIM card is faulty - SimUnknown // SIM card is unknown - } + @Override + public void finishedWakingUp() { + setSelected(true); + } + }; public CarrierText(Context context) { this(context, null); @@ -125,8 +63,6 @@ public class CarrierText extends TextView { public CarrierText(Context context, AttributeSet attrs) { super(context, attrs); - mIsEmergencyCallCapable = context.getResources().getBoolean( - com.android.internal.R.bool.config_voice_capable); boolean useAllCaps; TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.CarrierText, 0, 0); @@ -138,132 +74,6 @@ public class CarrierText extends TextView { a.recycle(); } setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps)); - - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - } - - /** - * Checks if there are faulty cards. Adds the text depending on the slot of the card - * @param text: current carrier text based on the sim state - * @param noSims: whether a valid sim card is inserted - * @return text - */ - private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) { - final CharSequence carrier = ""; - CharSequence carrierTextForSimIOError = getCarrierTextForSimState( - IccCardConstants.State.CARD_IO_ERROR, carrier); - for (int index = 0; index < mSimErrorState.length; index++) { - if (mSimErrorState[index]) { - // In the case when no sim cards are detected but a faulty card is inserted - // overwrite the text and only show "Invalid card" - if (noSims) { - return concatenate(carrierTextForSimIOError, - getContext().getText(com.android.internal.R.string.emergency_calls_only)); - } else if (index == 0) { - // prepend "Invalid card" when faulty card is inserted in slot 0 - text = concatenate(carrierTextForSimIOError, text); - } else { - // concatenate "Invalid card" when faulty card is inserted in slot 1 - text = concatenate(text, carrierTextForSimIOError); - } - } - } - return text; - } - - protected void updateCarrierText() { - boolean allSimsMissing = true; - boolean anySimReadyAndInService = false; - CharSequence displayText = null; - - List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); - final int N = subs.size(); - if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N); - for (int i = 0; i < N; i++) { - int subId = subs.get(i).getSubscriptionId(); - State simState = mKeyguardUpdateMonitor.getSimState(subId); - CharSequence carrierName = subs.get(i).getCarrierName(); - CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); - if (DEBUG) { - Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); - } - if (carrierTextForSimState != null) { - allSimsMissing = false; - displayText = concatenate(displayText, carrierTextForSimState); - } - if (simState == IccCardConstants.State.READY) { - ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); - if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) { - // hack for WFC (IWLAN) not turning off immediately once - // Wi-Fi is disassociated or disabled - if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN - || (mWifiManager.isWifiEnabled() - && mWifiManager.getConnectionInfo() != null - && mWifiManager.getConnectionInfo().getBSSID() != null)) { - if (DEBUG) { - Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); - } - anySimReadyAndInService = true; - } - } - } - } - if (allSimsMissing) { - if (N != 0) { - // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. - // This depends on mPlmn containing the text "Emergency calls only" when the radio - // has some connectivity. Otherwise, it should be null or empty and just show - // "No SIM card" - // Grab the first subscripton, because they all should contain the emergency text, - // described above. - displayText = makeCarrierStringOnEmergencyCapable( - getMissingSimMessage(), subs.get(0).getCarrierName()); - } else { - // We don't have a SubscriptionInfo to get the emergency calls only from. - // Grab it from the old sticky broadcast if possible instead. We can use it - // here because no subscriptions are active, so we don't have - // to worry about MSIM clashing. - CharSequence text = - getContext().getText(com.android.internal.R.string.emergency_calls_only); - Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); - if (i != null) { - String spn = ""; - String plmn = ""; - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { - spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN); - } - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { - plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); - } - if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); - if (Objects.equals(plmn, spn)) { - text = plmn; - } else { - text = concatenate(plmn, spn); - } - } - displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); - } - } - - displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing); - // APM (airplane mode) != no carrier state. There are carrier services - // (e.g. WFC = Wi-Fi calling) which may operate in APM. - if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { - displayText = getAirplaneModeMessage(); - } - setText(displayText); - } - - private String getMissingSimMessage() { - return mShowMissingSim && mTelephonyCapable - ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; - } - - private String getAirplaneModeMessage() { - return mShowAirplaneMode - ? getContext().getString(R.string.airplane_mode) : ""; } @Override @@ -271,36 +81,27 @@ public class CarrierText extends TextView { super.onFinishInflate(); mSeparator = getResources().getString( com.android.internal.R.string.kg_text_message_separator); - boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); - setSelected(shouldMarquee); // Allow marquee to work. + mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode, + mShowMissingSim); + mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); + setSelected(mShouldMarquee); // Allow marquee to work. } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (ConnectivityManager.from(mContext).isNetworkSupported( - ConnectivityManager.TYPE_MOBILE)) { - mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); - mKeyguardUpdateMonitor.registerCallback(mCallback); - } else { - // Don't listen and clear out the text when the device isn't a phone. - mKeyguardUpdateMonitor = null; - setText(""); - } + mCarrierTextController.setListening(mCarrierTextCallback); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mKeyguardUpdateMonitor != null) { - mKeyguardUpdateMonitor.removeCallback(mCallback); - } + mCarrierTextController.setListening(null); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); - // Only show marquee when visible if (visibility == VISIBLE) { setEllipsize(TextUtils.TruncateAt.MARQUEE); @@ -309,167 +110,6 @@ public class CarrierText extends TextView { } } - /** - * Top-level function for creating carrier text. Makes text based on simState, PLMN - * and SPN as well as device capabilities, such as being emergency call capable. - * - * @param simState - * @param text - * @param spn - * @return Carrier text if not in missing state, null otherwise. - */ - private CharSequence getCarrierTextForSimState(IccCardConstants.State simState, - CharSequence text) { - CharSequence carrierText = null; - StatusMode status = getStatusForIccState(simState); - switch (status) { - case Normal: - carrierText = text; - break; - - case SimNotReady: - // Null is reserved for denoting missing, in this case we have nothing to display. - carrierText = ""; // nothing to display yet. - break; - - case NetworkLocked: - carrierText = makeCarrierStringOnEmergencyCapable( - mContext.getText(R.string.keyguard_network_locked_message), text); - break; - - case SimMissing: - carrierText = null; - break; - - case SimPermDisabled: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText( - R.string.keyguard_permanent_disabled_sim_message_short), - text); - break; - - case SimMissingLocked: - carrierText = null; - break; - - case SimLocked: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_locked_message), - text); - break; - - case SimPukLocked: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_puk_locked_message), - text); - break; - case SimIoError: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_error_message_short), - text); - break; - case SimUnknown: - carrierText = null; - break; - } - - return carrierText; - } - - /* - * Add emergencyCallMessage to carrier string only if phone supports emergency calls. - */ - private CharSequence makeCarrierStringOnEmergencyCapable( - CharSequence simMessage, CharSequence emergencyCallMessage) { - if (mIsEmergencyCallCapable) { - return concatenate(simMessage, emergencyCallMessage); - } - return simMessage; - } - - /** - * Determine the current status of the lock screen given the SIM state and other stuff. - */ - private StatusMode getStatusForIccState(IccCardConstants.State simState) { - // Since reading the SIM may take a while, we assume it is present until told otherwise. - if (simState == null) { - return StatusMode.Normal; - } - - final boolean missingAndNotProvisioned = - !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned() - && (simState == IccCardConstants.State.ABSENT || - simState == IccCardConstants.State.PERM_DISABLED); - - // Assume we're NETWORK_LOCKED if not provisioned - simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; - switch (simState) { - case ABSENT: - return StatusMode.SimMissing; - case NETWORK_LOCKED: - return StatusMode.SimMissingLocked; - case NOT_READY: - return StatusMode.SimNotReady; - case PIN_REQUIRED: - return StatusMode.SimLocked; - case PUK_REQUIRED: - return StatusMode.SimPukLocked; - case READY: - return StatusMode.Normal; - case PERM_DISABLED: - return StatusMode.SimPermDisabled; - case UNKNOWN: - return StatusMode.SimUnknown; - case CARD_IO_ERROR: - return StatusMode.SimIoError; - } - return StatusMode.SimUnknown; - } - - private static CharSequence concatenate(CharSequence plmn, CharSequence spn) { - final boolean plmnValid = !TextUtils.isEmpty(plmn); - final boolean spnValid = !TextUtils.isEmpty(spn); - if (plmnValid && spnValid) { - return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString(); - } else if (plmnValid) { - return plmn; - } else if (spnValid) { - return spn; - } else { - return ""; - } - } - - private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState, - String plmn, String spn) { - int carrierHelpTextId = 0; - StatusMode status = getStatusForIccState(simState); - switch (status) { - case NetworkLocked: - carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; - break; - - case SimMissing: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; - break; - - case SimPermDisabled: - carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; - break; - - case SimMissingLocked: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions; - break; - - case Normal: - case SimLocked: - case SimPukLocked: - break; - } - - return mContext.getText(carrierHelpTextId); - } - private class CarrierTextTransformationMethod extends SingleLineTransformationMethod { private final Locale mLocale; private final boolean mAllCaps; diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java new file mode 100644 index 000000000000..2ce69650b65c --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.telephony.ServiceState; +import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; +import com.android.settingslib.WirelessUtils; +import com.android.systemui.Dependency; +import com.android.systemui.keyguard.WakefulnessLifecycle; + +import java.util.List; +import java.util.Objects; + +/** + * Controller that generates text including the carrier names and/or the status of all the SIM + * interfaces in the device. Through a callback, the updates can be retrieved either as a list or + * separated by a given separator {@link CharSequence}. + */ +public class CarrierTextController { + private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final String TAG = "CarrierTextController"; + + private final boolean mIsEmergencyCallCapable; + private boolean mTelephonyCapable; + private boolean mShowMissingSim; + private boolean mShowAirplaneMode; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private WifiManager mWifiManager; + private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()]; + private CarrierTextCallback mCarrierTextCallback; + private Context mContext; + private CharSequence mSeparator; + private WakefulnessLifecycle mWakefulnessLifecycle; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + @Override + public void onFinishedWakingUp() { + mCarrierTextCallback.finishedWakingUp(); + } + + @Override + public void onStartedGoingToSleep() { + mCarrierTextCallback.startedGoingToSleep(); + } + }; + + private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onRefreshCarrierInfo() { + if (DEBUG) { + Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " + + Boolean.toString(mTelephonyCapable)); + } + updateCarrierText(); + } + + @Override + public void onTelephonyCapable(boolean capable) { + if (DEBUG) { + Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " + + Boolean.toString(capable)); + } + mTelephonyCapable = capable; + updateCarrierText(); + } + + public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { + if (slotId < 0) { + Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId + + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); + return; + } + + if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState)); + if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) { + mSimErrorState[slotId] = true; + updateCarrierText(); + } else if (mSimErrorState[slotId]) { + mSimErrorState[slotId] = false; + updateCarrierText(); + } + } + }; + + /** + * The status of this lock screen. Primarily used for widgets on LockScreen. + */ + private enum StatusMode { + Normal, // Normal case (sim card present, it's not locked) + NetworkLocked, // SIM card is 'network locked'. + SimMissing, // SIM card is missing. + SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access + SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times + SimLocked, // SIM card is currently locked + SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure + SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. + SimIoError, // SIM card is faulty + SimUnknown // SIM card is unknown + } + + /** + * Controller that provides updates on text with carriers names or SIM status. + * Used by {@link CarrierText}. + * + * @param separator Separator between different parts of the text + */ + public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode, + boolean showMissingSim) { + mContext = context; + mIsEmergencyCallCapable = context.getResources().getBoolean( + com.android.internal.R.bool.config_voice_capable); + + mShowAirplaneMode = showAirplaneMode; + mShowMissingSim = showMissingSim; + + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mSeparator = separator; + mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); + } + + /** + * Checks if there are faulty cards. Adds the text depending on the slot of the card + * + * @param text: current carrier text based on the sim state + * @param noSims: whether a valid sim card is inserted + * @return text + */ + private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) { + final CharSequence carrier = ""; + CharSequence carrierTextForSimIOError = getCarrierTextForSimState( + IccCardConstants.State.CARD_IO_ERROR, carrier); + for (int index = 0; index < mSimErrorState.length; index++) { + if (mSimErrorState[index]) { + // In the case when no sim cards are detected but a faulty card is inserted + // overwrite the text and only show "Invalid card" + if (noSims) { + return concatenate(carrierTextForSimIOError, + getContext().getText( + com.android.internal.R.string.emergency_calls_only), + mSeparator); + } else if (index == 0) { + // prepend "Invalid card" when faulty card is inserted in slot 0 + text = concatenate(carrierTextForSimIOError, text, mSeparator); + } else { + // concatenate "Invalid card" when faulty card is inserted in slot 1 + text = concatenate(text, carrierTextForSimIOError, mSeparator); + } + } + } + return text; + } + + /** + * Sets the listening status of this controller. If the callback is null, it is set to + * not listening + * + * @param callback Callback to provide text updates + */ + public void setListening(CarrierTextCallback callback) { + if (callback != null) { + mCarrierTextCallback = callback; + if (ConnectivityManager.from(mContext).isNetworkSupported( + ConnectivityManager.TYPE_MOBILE)) { + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + mKeyguardUpdateMonitor.registerCallback(mCallback); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + } else { + // Don't listen and clear out the text when the device isn't a phone. + mKeyguardUpdateMonitor = null; + callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null)); + } + } else { + mCarrierTextCallback = null; + if (mKeyguardUpdateMonitor != null) { + mKeyguardUpdateMonitor.removeCallback(mCallback); + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); + } + } + } + + protected void updateCarrierText() { + boolean allSimsMissing = true; + boolean anySimReadyAndInService = false; + CharSequence displayText = null; + + List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); + final int numSubs = subs.size(); + final int[] subsIds = new int[numSubs]; + if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs); + for (int i = 0; i < numSubs; i++) { + int subId = subs.get(i).getSubscriptionId(); + subsIds[i] = subId; + IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId); + CharSequence carrierName = subs.get(i).getCarrierName(); + CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); + if (DEBUG) { + Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); + } + if (carrierTextForSimState != null) { + allSimsMissing = false; + displayText = concatenate(displayText, carrierTextForSimState, mSeparator); + } + if (simState == IccCardConstants.State.READY) { + ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); + if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) { + // hack for WFC (IWLAN) not turning off immediately once + // Wi-Fi is disassociated or disabled + if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + || (mWifiManager.isWifiEnabled() + && mWifiManager.getConnectionInfo() != null + && mWifiManager.getConnectionInfo().getBSSID() != null)) { + if (DEBUG) { + Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); + } + anySimReadyAndInService = true; + } + } + } + } + if (allSimsMissing) { + if (numSubs != 0) { + // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. + // This depends on mPlmn containing the text "Emergency calls only" when the radio + // has some connectivity. Otherwise, it should be null or empty and just show + // "No SIM card" + // Grab the first subscripton, because they all should contain the emergency text, + // described above. + displayText = makeCarrierStringOnEmergencyCapable( + getMissingSimMessage(), subs.get(0).getCarrierName()); + } else { + // We don't have a SubscriptionInfo to get the emergency calls only from. + // Grab it from the old sticky broadcast if possible instead. We can use it + // here because no subscriptions are active, so we don't have + // to worry about MSIM clashing. + CharSequence text = + getContext().getText(com.android.internal.R.string.emergency_calls_only); + Intent i = getContext().registerReceiver(null, + new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); + if (i != null) { + String spn = ""; + String plmn = ""; + if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { + spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN); + } + if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { + plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); + } + if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); + if (Objects.equals(plmn, spn)) { + text = plmn; + } else { + text = concatenate(plmn, spn, mSeparator); + } + } + displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); + } + } + + displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing); + // APM (airplane mode) != no carrier state. There are carrier services + // (e.g. WFC = Wi-Fi calling) which may operate in APM. + if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { + displayText = getAirplaneModeMessage(); + } + + if (mCarrierTextCallback != null) { + mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo( + displayText, + displayText.toString().split(mSeparator.toString()), + anySimReadyAndInService, + subsIds)); + } + + } + + private Context getContext() { + return mContext; + } + + private String getMissingSimMessage() { + return mShowMissingSim && mTelephonyCapable + ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; + } + + private String getAirplaneModeMessage() { + return mShowAirplaneMode + ? getContext().getString(R.string.airplane_mode) : ""; + } + + /** + * Top-level function for creating carrier text. Makes text based on simState, PLMN + * and SPN as well as device capabilities, such as being emergency call capable. + * + * @return Carrier text if not in missing state, null otherwise. + */ + private CharSequence getCarrierTextForSimState(IccCardConstants.State simState, + CharSequence text) { + CharSequence carrierText = null; + CarrierTextController.StatusMode status = getStatusForIccState(simState); + switch (status) { + case Normal: + carrierText = text; + break; + + case SimNotReady: + // Null is reserved for denoting missing, in this case we have nothing to display. + carrierText = ""; // nothing to display yet. + break; + + case NetworkLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + mContext.getText(R.string.keyguard_network_locked_message), text); + break; + + case SimMissing: + carrierText = null; + break; + + case SimPermDisabled: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText( + R.string.keyguard_permanent_disabled_sim_message_short), + text); + break; + + case SimMissingLocked: + carrierText = null; + break; + + case SimLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.keyguard_sim_locked_message), + text); + break; + + case SimPukLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.keyguard_sim_puk_locked_message), + text); + break; + case SimIoError: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.keyguard_sim_error_message_short), + text); + break; + case SimUnknown: + carrierText = null; + break; + } + + return carrierText; + } + + /* + * Add emergencyCallMessage to carrier string only if phone supports emergency calls. + */ + private CharSequence makeCarrierStringOnEmergencyCapable( + CharSequence simMessage, CharSequence emergencyCallMessage) { + if (mIsEmergencyCallCapable) { + return concatenate(simMessage, emergencyCallMessage, mSeparator); + } + return simMessage; + } + + /** + * Determine the current status of the lock screen given the SIM state and other stuff. + */ + private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) { + // Since reading the SIM may take a while, we assume it is present until told otherwise. + if (simState == null) { + return CarrierTextController.StatusMode.Normal; + } + + final boolean missingAndNotProvisioned = + !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned() + && (simState == IccCardConstants.State.ABSENT + || simState == IccCardConstants.State.PERM_DISABLED); + + // Assume we're NETWORK_LOCKED if not provisioned + simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; + switch (simState) { + case ABSENT: + return CarrierTextController.StatusMode.SimMissing; + case NETWORK_LOCKED: + return CarrierTextController.StatusMode.SimMissingLocked; + case NOT_READY: + return CarrierTextController.StatusMode.SimNotReady; + case PIN_REQUIRED: + return CarrierTextController.StatusMode.SimLocked; + case PUK_REQUIRED: + return CarrierTextController.StatusMode.SimPukLocked; + case READY: + return CarrierTextController.StatusMode.Normal; + case PERM_DISABLED: + return CarrierTextController.StatusMode.SimPermDisabled; + case UNKNOWN: + return CarrierTextController.StatusMode.SimUnknown; + case CARD_IO_ERROR: + return CarrierTextController.StatusMode.SimIoError; + } + return CarrierTextController.StatusMode.SimUnknown; + } + + private static CharSequence concatenate(CharSequence plmn, CharSequence spn, + CharSequence separator) { + final boolean plmnValid = !TextUtils.isEmpty(plmn); + final boolean spnValid = !TextUtils.isEmpty(spn); + if (plmnValid && spnValid) { + return new StringBuilder().append(plmn).append(separator).append(spn).toString(); + } else if (plmnValid) { + return plmn; + } else if (spnValid) { + return spn; + } else { + return ""; + } + } + + private static List<CharSequence> append(List<CharSequence> list, CharSequence string) { + if (!TextUtils.isEmpty(string)) { + list.add(string); + } + return list; + } + + private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState, + String plmn, String spn) { + int carrierHelpTextId = 0; + CarrierTextController.StatusMode status = getStatusForIccState(simState); + switch (status) { + case NetworkLocked: + carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; + break; + + case SimMissing: + carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; + break; + + case SimPermDisabled: + carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; + break; + + case SimMissingLocked: + carrierHelpTextId = R.string.keyguard_missing_sim_instructions; + break; + + case Normal: + case SimLocked: + case SimPukLocked: + break; + } + + return mContext.getText(carrierHelpTextId); + } + + /** + * Data structure for passing information to CarrierTextController subscribers + */ + public static final class CarrierTextCallbackInfo { + public final CharSequence carrierText; + public final CharSequence[] listOfCarriers; + public final boolean anySimReady; + public final int[] subscriptionIds; + + CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, + boolean anySimReady, int[] subscriptionIds) { + this.carrierText = carrierText; + this.listOfCarriers = listOfCarriers; + this.anySimReady = anySimReady; + this.subscriptionIds = subscriptionIds; + } + } + + /** + * Callback to communicate to Views + */ + public interface CarrierTextCallback { + /** + * Provides updated carrier information. + */ + default void updateCarrierInfo(CarrierTextCallbackInfo info) {}; + + /** + * Notifies the View that the device is going to sleep + */ + default void startedGoingToSleep() {}; + + /** + * Notifies the View that the device finished waking up + */ + default void finishedWakingUp() {}; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index d99f234c26c8..fece94e69a3d 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -44,6 +44,7 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; +import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.AmbientPulseManager; @@ -278,6 +279,7 @@ public class Dependency extends SystemUI { @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; @Inject Lazy<AutoHideController> mAutoHideController; @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener; + @Inject Lazy<PrivacyItemController> mPrivacyItemController; @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper; @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler; @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler; @@ -452,6 +454,8 @@ public class Dependency extends SystemUI { mProviders.put(ForegroundServiceNotificationListener.class, mForegroundServiceNotificationListener::get); mProviders.put(ClockManager.class, mClockManager::get); + mProviders.put(PrivacyItemController.class, mPrivacyItemController::get); + // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index 0c7a9a9fffd2..85265f458370 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -26,11 +26,11 @@ import android.widget.LinearLayout; * Layout class representing the Global Actions menu which appears when the power button is held. */ public abstract class MultiListLayout extends LinearLayout { - boolean mHasOutsideTouch; - boolean mHasSeparatedView; + protected boolean mHasOutsideTouch; + protected boolean mHasSeparatedView; - int mExpectedSeparatedItemCount; - int mExpectedListItemCount; + protected int mExpectedSeparatedItemCount; + protected int mExpectedListItemCount; public MultiListLayout(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 92d3cc1ae34f..36a813b914d5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -57,7 +57,7 @@ public class BadgedImageView extends ImageView { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setScaleType(ScaleType.CENTER_CROP); - mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); + mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size); mDotRenderer = new BadgeRenderer(mIconSize); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index a457deed7ba4..b7bee30dc640 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -18,9 +18,8 @@ package com.android.systemui.bubbles; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain; @@ -229,10 +228,6 @@ public class BubbleController { } mStackView.stackDismissed(); - // Reset the position of the stack (TODO - or should we save / respect last user position?) - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - updateVisibility(); mNotificationEntryManager.updateNotifications(); } @@ -249,16 +244,14 @@ public class BubbleController { BubbleView bubble = mBubbles.get(notif.key); mStackView.updateBubble(bubble, notif, updatePosition); } else { - boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; if (mStackView == null) { - setPosition = true; mStackView = new BubbleStackView(mContext); ViewGroup sbv = mStatusBarWindowController.getStatusBarView(); // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim // between bubble and the shade int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1; sbv.addView(mStackView, bubblePosition, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } @@ -273,11 +266,6 @@ public class BubbleController { } mBubbles.put(bubble.getKey(), bubble); mStackView.addBubble(bubble); - if (setPosition) { - // Need to add the bubble to the stack before we can know the width - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); - mStackView.setPosition(startPoint.x, startPoint.y); - } } updateVisibility(); } @@ -423,24 +411,6 @@ public class BubbleController { return mStackView; } - // TODO: factor in PIP location / maybe last place user had it - /** - * Gets an appropriate starting point to position the bubble stack. - */ - private static Point getStartPoint(int size, Point displaySize) { - final int x = displaySize.x - size + EDGE_OVERLAP; - final int y = displaySize.y / 4; - return new Point(x, y); - } - - /** - * Gets an appropriate position for the bubble when the stack is expanded. - */ - static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) { - // Same place for now.. - return new Point(EDGE_OVERLAP, size); - } - /** * Whether the notification has been developer configured to bubble and is allowed by the user. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java deleted file mode 100644 index c1063fa54ff2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2018 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.bubbles; - -import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; - -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.view.View; -import android.view.WindowManager; - -import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView; - -import java.util.Arrays; - -/** - * Math and animators to move bubbles around the screen. - * - * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip - * movements can be unified maybe? - */ -public class BubbleMovementHelper { - - private static final int MAGNET_ANIM_TIME = 150; - public static final int EDGE_OVERLAP = 0; - - private Context mContext; - private Point mDisplaySize; - - public BubbleMovementHelper(Context context) { - mContext = context; - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mDisplaySize = new Point(); - wm.getDefaultDisplay().getSize(mDisplaySize); - } - - /** - * @return the distance between the two provided points. - */ - static double distance(Point p1, Point p2) { - return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); - } - - /** - * @return the y value of a line defined by y = mx+b - */ - static float findY(float m, float b, float x) { - return (m * x) + b; - } - - /** - * @return the x value of a line defined by y = mx+b - */ - static float findX(float m, float b, float y) { - return (y - b) / m; - } - - /** - * Determines a point on the edge of the screen based on the velocity and position. - */ - public Point getPointOnEdge(View bv, Point p, float velX, float velY) { - // Find the slope and the y-intercept - velX = velX == 0 ? 1 : velX; - final float m = velY / velX; - final float b = p.y - m * p.x; - - // There are two lines it can intersect, find the two points - Point pointHoriz = new Point(); - Point pointVert = new Point(); - - if (velX > 0) { - // right - pointHoriz.x = mDisplaySize.x; - pointHoriz.y = (int) findY(m, b, mDisplaySize.x); - } else { - // left - pointHoriz.x = EDGE_OVERLAP; - pointHoriz.y = (int) findY(m, b, 0); - } - if (velY > 0) { - // bottom - pointVert.x = (int) findX(m, b, mDisplaySize.y); - pointVert.y = mDisplaySize.y - getNavBarHeight(); - } else { - // top - pointVert.x = (int) findX(m, b, 0); - pointVert.y = EDGE_OVERLAP; - } - - // Use the point that's closest to the start position - final double distanceToVertPoint = distance(p, pointVert); - final double distanceToHorizPoint = distance(p, pointHoriz); - boolean useVert = distanceToVertPoint < distanceToHorizPoint; - // Check if we're being flung along the current edge, use opposite point in this case - // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but - // this works well enough for now - if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) { - // Flinging along left or right edge, favor vert edge - useVert = true; - - } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) { - // Flinging along top or bottom edge - useVert = false; - } - - if (useVert) { - pointVert.x = capX(pointVert.x, bv); - pointVert.y = capY(pointVert.y, bv); - return pointVert; - - } - pointHoriz.x = capX(pointHoriz.x, bv); - pointHoriz.y = capY(pointHoriz.y, bv); - return pointHoriz; - } - - /** - * @return whether the view is on a side edge of the screen (i.e. left or right). - */ - public boolean onSideEdge(View fv, Point p) { - return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x - - EDGE_OVERLAP - || p.x >= EDGE_OVERLAP; - } - - /** - * @return whether the view is on a top or bottom edge of the screen. - */ - public boolean onTopBotEdge(View bv, Point p) { - return p.y >= getStatusBarHeight() + EDGE_OVERLAP - || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y - - EDGE_OVERLAP; - } - - /** - * @return constrained x value based on screen size and how much a view can overlap with a side - * edge. - */ - public int capX(float x, View bv) { - // Floating things can't stick to top or bottom edges, so figure out if it's closer to - // left or right and just use that side + the overlap. - final float centerX = x + bv.getWidth() / 2; - if (centerX > mDisplaySize.x / 2) { - // Right side - return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP; - } else { - // Left side - return EDGE_OVERLAP; - } - } - - /** - * @return constrained y value based on screen size and how much a view can overlap with a top - * or bottom edge. - */ - public int capY(float y, View bv) { - final int height = bv.getHeight(); - if (y < getStatusBarHeight() + EDGE_OVERLAP) { - return getStatusBarHeight() + EDGE_OVERLAP; - } - if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) { - return mDisplaySize.y - height - EDGE_OVERLAP; - } - return (int) y; - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet animateMagnetTo(final BubbleStackView bv) { - Point pos = bv.getPosition(); - - // Find the distance to each edge - final int leftDistance = pos.x; - final int rightDistance = mDisplaySize.x - leftDistance; - final int topDistance = pos.y; - final int botDistance = mDisplaySize.y - topDistance; - - int smallest; - // Find the closest one - int[] distances = { - leftDistance, rightDistance, topDistance, botDistance - }; - Arrays.sort(distances); - smallest = distances[0]; - - // Animate to the closest edge - Point p = new Point(); - if (smallest == leftDistance) { - p.x = capX(EDGE_OVERLAP, bv); - p.y = capY(topDistance, bv); - } - if (smallest == rightDistance) { - p.x = capX(mDisplaySize.x, bv); - p.y = capY(topDistance, bv); - } - if (smallest == topDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(0, bv); - } - if (smallest == botDistance) { - p.x = capX(leftDistance, bv); - p.y = capY(mDisplaySize.y, bv); - } - return getTranslateAnim(bv, p, MAGNET_ANIM_TIME); - } - - /** - * Animation to fling the provided view. - */ - public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) { - Point pos = bv.getPosition(); - Point endPos = getPointOnEdge(bv, pos, velX, velY); - endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv)); - final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2) - + Math.pow(endPos.y - pos.y, 2)); - final float sumVel = Math.abs(velX) + Math.abs(velY); - final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50); - return getTranslateAnim(bv, endPos, duration); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) { - return getTranslateAnim(v, p, duration, 0); - } - - /** - * Animation to translate the provided view. - */ - public AnimatorSet getTranslateAnim(final FloatingView v, Point p, - int duration, int startDelay) { - return getTranslateAnim(v, p, duration, startDelay, null); - } - - /** - * Animation to translate the provided view. - * - * @param v the view to translate. - * @param p the point to translate to. - * @param duration the duration of the animation. - * @param startDelay the start delay of the animation. - * @param listener the listener to add to the animation. - * - * @return the animation. - */ - public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration, - int startDelay, AnimatorListener listener) { - Point curPos = v.getPosition(); - final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x); - animX.setDuration(duration); - animX.setStartDelay(startDelay); - animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionX((int) value); - } - }); - - final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y); - animY.setDuration(duration); - animY.setStartDelay(startDelay); - animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - v.setPositionY((int) value); - } - }); - if (listener != null) { - animY.addListener(listener); - } - - AnimatorSet set = new AnimatorSet(); - set.playTogether(animX, animY); - set.setInterpolator(FAST_OUT_SLOW_IN); - return set; - } - - - // TODO -- now that this is in system we should be able to get these better, but ultimately - // makes more sense to move to movement bounds style a la PIP - /** - * Returns the status bar height. - */ - public int getStatusBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } - - /** - * Returns the status bar height. - */ - public int getNavBarHeight() { - Resources res = mContext.getResources(); - int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId > 0) { - return res.getDimensionPixelSize(resourceId); - } - return 0; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index dcd121bdb239..5fdf76f6f0bc 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -16,59 +16,88 @@ package com.android.systemui.bubbles; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.app.ActivityView; import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.R; +import com.android.systemui.bubbles.animation.ExpandedAnimationController; +import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; +import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; -import com.android.systemui.statusbar.notification.stack.ViewState; /** * Renders bubbles in a stack and handles animating expanded and collapsed states. */ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView { - private static final String TAG = "BubbleStackView"; + + /** + * Friction applied to fling animations. Since the stack must land on one of the sides of the + * screen, we want less friction horizontally so that the stack has a better chance of making it + * to the side without needing a spring. + */ + private static final float FLING_FRICTION_X = 1.15f; + private static final float FLING_FRICTION_Y = 1.5f; + + /** + * Damping ratio to use for the stack spring animation used to spring the stack to its final + * position after a fling. + */ + private static final float SPRING_DAMPING_RATIO = 0.85f; + + /** + * Minimum fling velocity required to trigger moving the stack from one side of the screen to + * the other. + */ + private static final float ESCAPE_VELOCITY = 750f; + private Point mDisplaySize; - private FrameLayout mBubbleContainer; + private final SpringAnimation mExpandedViewXAnim; + private final SpringAnimation mExpandedViewYAnim; + + private PhysicsAnimationLayout mBubbleContainer; + private StackAnimationController mStackAnimationController; + private ExpandedAnimationController mExpandedAnimationController; + private BubbleExpandedViewContainer mExpandedViewContainer; private int mBubbleSize; private int mBubblePadding; + private int mExpandedAnimateXDistance; + private int mExpandedAnimateYDistance; private boolean mIsExpanded; private int mExpandedBubbleHeight; private BubbleTouchHandler mTouchHandler; private BubbleView mExpandedBubble; - private Point mCollapsedPosition; private BubbleController.BubbleExpandListener mExpandListener; private boolean mViewUpdatedRequested = false; @@ -110,8 +139,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F setOnTouchListener(mTouchHandler); Resources res = getResources(); - mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mExpandedAnimateXDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance); + mExpandedAnimateYDistance = + res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance); mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); @@ -120,6 +153,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + + mStackAnimationController = new StackAnimationController(); + mExpandedAnimationController = new ExpandedAnimationController(); + + mBubbleContainer = new PhysicsAnimationLayout(context); + mBubbleContainer.setMaxRenderedChildren( + getResources().getInteger(R.integer.bubbles_max_rendered)); + mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setElevation(elevation); + mBubbleContainer.setPadding(padding, 0, padding, 0); + mBubbleContainer.setClipChildren(false); + addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + mExpandedViewContainer = (BubbleExpandedViewContainer) LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view, this /* parent */, false /* attachToRoot */); @@ -128,11 +174,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); - mBubbleContainer = new FrameLayout(context); - mBubbleContainer.setElevation(elevation); - mBubbleContainer.setPadding(padding, 0, padding, 0); - mBubbleContainer.setClipChildren(false); - addView(mBubbleContainer); + mExpandedViewXAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); + mExpandedViewXAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); + + mExpandedViewYAnim = + new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y); + mExpandedViewYAnim.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); setClipChildren(false); } @@ -144,38 +198,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } @Override - public void onMeasure(int widthSpec, int heightSpec) { - super.onMeasure(widthSpec, heightSpec); - - int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - if (mIsExpanded) { - ViewGroup parent = (ViewGroup) getParent(); - int parentWidth = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY); - int parentHeight = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec); - - int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec), - MeasureSpec.UNSPECIFIED); - measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight); - setMeasuredDimension(widthSpec, parentHeight); - } else { - // Not expanded - measureChild(mExpandedViewContainer, 0, 0); - - // Bubbles are translated a little to stack on top of each other - widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY); - measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec); - - heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(), - MeasureSpec.EXACTLY); - setMeasuredDimension(widthSpec, heightSpec); - } - } - - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { float x = ev.getRawX(); float y = ev.getRawY(); @@ -293,9 +315,11 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F boolean updatePosition) { bubbleView.update(entry); if (updatePosition && !mIsExpanded) { - // If alerting it gets promoted to top of the stack - mBubbleContainer.removeView(bubbleView); - mBubbleContainer.addView(bubbleView, 0); + // If alerting it gets promoted to top of the stack. + if (mBubbleContainer.indexOfChild(bubbleView) != 0) { + mBubbleContainer.removeViewAndThen(bubbleView, + () -> mBubbleContainer.addView(bubbleView, 0)); + } requestUpdate(); } if (mIsExpanded && bubbleView.equals(mExpandedBubble)) { @@ -359,36 +383,51 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (mIsExpanded != shouldExpand) { mIsExpanded = shouldExpand; updateExpandedBubble(); + applyCurrentState(); + //requestUpdate(); + + mIsAnimating = true; + + Runnable updateAfter = () -> { + applyCurrentState(); + mIsAnimating = false; + requestUpdate(); + }; if (shouldExpand) { - // Save current position so that we might return there - savePosition(); + mBubbleContainer.setController(mExpandedAnimationController); + mExpandedAnimationController.expandFromStack( + mStackAnimationController.getStackPosition(), updateAfter); + } else { + mBubbleContainer.cancelAllAnimations(); + mExpandedAnimationController.collapseBackToStack( + () -> { + mBubbleContainer.setController(mStackAnimationController); + updateAfter.run(); + }); } - // Determine the translation for the stack - Point position = shouldExpand - ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize) - : mCollapsedPosition; - int delay = shouldExpand ? 0 : 100; - AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position, - 200, delay, null); - if (!shouldExpand) { - // First collapse the stack, then translate, maybe should expand at same time? - animateStackExpansion(() -> translationAnim.start()); - } else { - // First translate, then expand - translationAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mIsAnimating = true; - } - @Override - public void onAnimationEnd(Animator animation) { - animateStackExpansion(() -> mIsAnimating = false); - } - }); - translationAnim.start(); + final float xStart = + mStackAnimationController.getStackPosition().x < getWidth() / 2 + ? -mExpandedAnimateXDistance + : mExpandedAnimateXDistance; + + final float yStart = Math.min( + mStackAnimationController.getStackPosition().y, + mExpandedAnimateYDistance); + final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding; + + if (shouldExpand) { + mExpandedViewContainer.setTranslationX(xStart); + mExpandedViewContainer.setTranslationY(yStart); + mExpandedViewContainer.setAlpha(0f); } + + mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart); + mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart); + mExpandedViewContainer.animate() + .setDuration(100) + .alpha(shouldExpand ? 1f : 0f); } } @@ -401,14 +440,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F + mBubbleContainer.getPaddingStart(); } - /** - * Saves the current position of the stack, used to save user placement of the stack to - * return to after an animation. - */ - private void savePosition() { - mCollapsedPosition = getPosition(); - } - private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) { if (mExpandListener != null) { NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null; @@ -420,31 +451,151 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F return getBubbleAt(0); } - private BubbleView getBubbleAt(int i) { + /** Return the BubbleView at the given index from the bubble container. */ + public BubbleView getBubbleAt(int i) { return mBubbleContainer.getChildCount() > i ? (BubbleView) mBubbleContainer.getChildAt(i) : null; } @Override - public void setPosition(int x, int y) { - setPositionX(x); - setPositionY(y); + public void setPosition(float x, float y) { + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); } @Override - public void setPositionX(int x) { - setTranslationX(x); + public void setPositionX(float x) { + // Unsupported, use setPosition(x, y). } @Override - public void setPositionY(int y) { - setTranslationY(y); + public void setPositionY(float y) { + // Unsupported, use setPosition(x, y). } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return mStackAnimationController.getStackPosition(); + } + + /** Called when a drag operation on an individual bubble has started. */ + public void onBubbleDragStart(BubbleView bubble) { + // TODO: Save position and snap back if not dismissed. + } + + /** Called with the coordinates to which an individual bubble has been dragged. */ + public void onBubbleDragged(BubbleView bubble, float x, float y) { + bubble.setTranslationX(x); + bubble.setTranslationY(y); + } + + /** Called when a drag operation on an individual bubble has finished. */ + public void onBubbleDragFinish(BubbleView bubble, float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + } + + void onDragStart() { + if (mIsExpanded) { + return; + } + + mStackAnimationController.cancelStackPositionAnimations(); + mBubbleContainer.setController(mStackAnimationController); + mIsAnimating = false; + } + + void onDragged(float x, float y) { + // TODO: We can drag if animating - just need to reroute inflight anims to drag point. + if (mIsExpanded) { + return; + } + + mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y); + } + + void onDragFinish(float x, float y, float velX, float velY) { + // TODO: Add fling to bottom to dismiss. + + if (mIsExpanded || mIsAnimating) { + return; + } + + final boolean stackOnLeftSide = x + - mBubbleContainer.getChildAt(0).getWidth() / 2 + < mDisplaySize.x / 2; + + final boolean stackShouldFlingLeft = stackOnLeftSide + ? velX < ESCAPE_VELOCITY + : velX < -ESCAPE_VELOCITY; + + final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion(); + + // Target X translation (either the left or right side of the screen). + final float destinationRelativeX = stackShouldFlingLeft + ? stackBounds.left : stackBounds.right; + + // Minimum velocity required for the stack to make it to the side of the screen. + final float escapeVelocity = getMinXVelocity( + x, + destinationRelativeX, + FLING_FRICTION_X); + + // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so + // that it'll make it all the way to the side of the screen. + final float startXVelocity = stackShouldFlingLeft + ? Math.min(escapeVelocity, velX) + : Math.max(escapeVelocity, velX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + startXVelocity, + FLING_FRICTION_X, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + destinationRelativeX); + + mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + velY, + FLING_FRICTION_Y, + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SPRING_DAMPING_RATIO), + /* destination */ null); + } + + /** + * Minimum velocity, in pixels/second, required to get from x to destX while being slowed by a + * given frictional force. + * + * This is not derived using real math, I just made it up because the math in FlingAnimation + * looks hard and this seems to work. It doesn't actually matter because if it doesn't make it + * to the edge via Fling, it'll get Spring'd there anyway. + * + * TODO(tsuji, or someone who likes math): Figure out math. + */ + private float getMinXVelocity(float x, float destX, float friction) { + return (destX - x) * (friction * 5) + ESCAPE_VELOCITY; + } + + @Override + public void getBoundsOnScreen(Rect outRect) { + if (!mIsExpanded) { + mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect); + } else { + mBubbleContainer.getBoundsOnScreen(outRect); + } + } + + private int getStatusBarHeight() { + if (getRootWindowInsets() != null) { + return Math.max( + getRootWindowInsets().getSystemWindowInsetTop(), + getRootWindowInsets().getDisplayCutout().getSafeInsetTop()); + } + + return 0; } private boolean isIntersecting(View view, float x, float y) { @@ -478,22 +629,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F final PendingIntent intent = mExpandedBubble.getAppOverlayIntent(); mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString()); mExpandedViewContainer.setExpandedView(expandedView); - expandedView.setCallback(new ActivityView.StateCallback() { - @Override - public void onActivityViewReady(ActivityView view) { - Log.d(TAG, "onActivityViewReady(" - + mExpandedBubble.getEntry().key + "): " + view); - view.startActivity(intent); - } - - @Override - public void onActivityViewDestroyed(ActivityView view) { - NotificationEntry entry = mExpandedBubble != null - ? mExpandedBubble.getEntry() : null; - Log.d(TAG, "onActivityViewDestroyed(key=" - + ((entry != null) ? entry.key : "(none)") + "): " + view); - } - }); } else { // Bubble with notification view expanded state ExpandableNotificationRow row = mExpandedBubble.getRowView(); @@ -510,9 +645,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedViewContainer.setHeaderText(null); } - int pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2); - mExpandedViewContainer.setPointerPosition(pointerPosition); + float pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition((int) pointerPosition); } private void applyCurrentState() { @@ -522,7 +656,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (!mIsExpanded) { mExpandedViewContainer.setExpandedView(null); } else { - mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); View expandedView = mExpandedViewContainer.getExpandedView(); if (expandedView instanceof ActivityView) { if (expandedView.isAttachedToWindow()) { @@ -537,53 +670,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.updateDotVisibility(); bv.setZ(bubbsCount - i); - - int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i; - ViewState viewState = new ViewState(); - viewState.initFrom(bv); - viewState.xTranslation = transX; - viewState.applyToView(bv); - - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - bv.setTag(new Point(transX, 0)); - } - } - } - - private void animateStackExpansion(Runnable endRunnable) { - int childCount = mBubbleContainer.getChildCount(); - for (int i = 0; i < childCount; i++) { - BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i); - int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i; - int duration = childCount > 1 ? 200 : 0; - if (mIsExpanded) { - // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler - child.setTag(new Point(transX, 0)); - } - ViewPropertyAnimator anim = child - .animate() - .setStartDelay(15 * i) - .setDuration(duration) - .setInterpolator(mIsExpanded - ? new OvershootInterpolator() - : new AccelerateInterpolator()) - .translationY(0) - .translationX(transX); - final int fi = i; - // Probably want this choreographed with translation somehow / make it snappier - anim.withStartAction(() -> mIsAnimating = true); - anim.withEndAction(() -> { - if (endRunnable != null) { - endRunnable.run(); - } - if (fi == mBubbleContainer.getChildCount() - 1) { - applyCurrentState(); - mIsAnimating = false; - requestUpdate(); - } - }); - anim.start(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 97784b0f4f93..22cd2fcc3e72 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -19,7 +19,7 @@ package com.android.systemui.bubbles; import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY; import android.content.Context; -import android.graphics.Point; +import android.graphics.PointF; import android.os.Handler; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -37,18 +37,16 @@ class BubbleTouchHandler implements View.OnTouchListener { private BubbleController mController = Dependency.get(BubbleController.class); private PipDismissViewController mDismissViewController; - private BubbleMovementHelper mMovementHelper; // The position of the bubble on down event - private int mBubbleDownPosX; - private int mBubbleDownPosY; + private float mBubbleDownPosX; + private float mBubbleDownPosY; // The touch position on down event - private int mDownX = -1; - private int mDownY = -1; + private float mDownX = -1; + private float mDownY = -1; private boolean mMovedEnough; private int mTouchSlopSquared; - private float mMinFlingVelocity; private VelocityTracker mVelocityTracker; private boolean mInDismissTarget; @@ -71,32 +69,27 @@ class BubbleTouchHandler implements View.OnTouchListener { /** * Sets the position of the view. */ - void setPosition(int x, int y); + void setPosition(float x, float y); /** * Sets the x position of the view. */ - void setPositionX(int x); + void setPositionX(float x); /** * Sets the y position of the view. */ - void setPositionY(int y); + void setPositionY(float y); /** * @return the position of the view. */ - Point getPosition(); + PointF getPosition(); } public BubbleTouchHandler(Context context) { final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mTouchSlopSquared = touchSlop * touchSlop; - - // Multiply by 3 for better fling - mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3; - - mMovementHelper = new BubbleMovementHelper(context); mDismissViewController = new PipDismissViewController(context); } @@ -119,9 +112,11 @@ class BubbleTouchHandler implements View.OnTouchListener { FloatingView floatingView = (FloatingView) targetView; boolean isBubbleStack = floatingView instanceof BubbleStackView; - Point startPos = floatingView.getPosition(); - int rawX = (int) event.getRawX(); - int rawY = (int) event.getRawY(); + PointF startPos = floatingView.getPosition(); + float rawX = event.getRawX(); + float rawY = event.getRawY(); + float x = mBubbleDownPosX + rawX - mDownX; + float y = mBubbleDownPosY + rawY - mDownY; switch (action) { case MotionEvent.ACTION_DOWN: trackMovement(event); @@ -134,6 +129,13 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; mMovedEnough = false; + + if (isBubbleStack) { + stack.onDragStart(); + } else { + stack.onBubbleDragStart((BubbleView) floatingView); + } + break; case MotionEvent.ACTION_MOVE: @@ -145,22 +147,23 @@ class BubbleTouchHandler implements View.OnTouchListener { mDownX = rawX; mDownY = rawY; } - final int deltaX = rawX - mDownX; - final int deltaY = rawY - mDownY; + final float deltaX = rawX - mDownX; + final float deltaY = rawY - mDownY; if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) { mMovedEnough = true; } - int x = mBubbleDownPosX + rawX - mDownX; - int y = mBubbleDownPosY + rawY - mDownY; if (mMovedEnough) { - if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) { + if (floatingView instanceof BubbleView) { mBubbleDraggingOut = ((BubbleView) floatingView); + stack.onBubbleDragged(mBubbleDraggingOut, x, y); + } else { + stack.onDragged(x, y); } - floatingView.setPosition(x, y); } // TODO - when we're in the target stick to it / animate in some way? - mInDismissTarget = mDismissViewController.updateTarget((View) floatingView); + mInDismissTarget = mDismissViewController.updateTarget( + isBubbleStack ? stack.getBubbleAt(0) : (View) floatingView); break; case MotionEvent.ACTION_CANCEL: @@ -181,19 +184,9 @@ class BubbleTouchHandler implements View.OnTouchListener { final float velX = mVelocityTracker.getXVelocity(); final float velY = mVelocityTracker.getYVelocity(); if (isBubbleStack) { - if ((Math.abs(velY) > mMinFlingVelocity) - || (Math.abs(velX) > mMinFlingVelocity)) { - // It's being flung somewhere - mMovementHelper.animateFlingTo(stack, velX, velY).start(); - } else { - // Magnet back to nearest edge - mMovementHelper.animateMagnetTo(stack).start(); - } + stack.onDragFinish(x, y, velX, velY); } else { - // Individual bubble got dragged but not dismissed.. lets animate it back - // into position - Point toGoTo = (Point) ((View) floatingView).getTag(); - mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start(); + stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY); } } else if (floatingView.equals(stack.getExpandedBubble())) { stack.collapseStack(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index e8432b9a7017..dc948325e27a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -22,7 +22,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; -import android.graphics.Point; +import android.graphics.PointF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -60,6 +60,8 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati private NotificationEntry mEntry; private PendingIntent mAppOverlayIntent; private ActivityView mActivityView; + private boolean mActivityViewReady; + private boolean mActivityViewStarted; public BubbleView(Context context) { this(context, null); @@ -192,10 +194,10 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati fraction = showDot ? fraction : 1 - fraction; mBadgedImageView.setDotScale(fraction); }).withEndAction(() -> { - if (!showDot) { - mBadgedImageView.setShowDot(false); - } - }).start(); + if (!showDot) { + mBadgedImageView.setShowDot(false); + } + }).start(); } } @@ -232,8 +234,21 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati */ public ActivityView getActivityView() { if (mActivityView == null) { - mActivityView = new ActivityView(mContext); + mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, + true /* singleTaskInstance */); Log.d(TAG, "[getActivityView] created: " + mActivityView); + mActivityView.setCallback(new ActivityView.StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + mActivityViewReady = true; + mActivityView.startActivity(mAppOverlayIntent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) { + mActivityViewReady = false; + } + }); } return mActivityView; } @@ -245,46 +260,44 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati if (mActivityView == null) { return; } - // HACK: Only release if initialized. There's no way to know if the ActivityView has - // been initialized. Calling release() if it hasn't been initialized will crash. - + if (!mActivityViewReady) { + // release not needed, never initialized? + mActivityView = null; + return; + } + // HACK: release() will crash if the view is not attached. if (!mActivityView.isAttachedToWindow()) { - // HACK: release() will crash if the view is not attached. - mActivityView.setVisibility(View.GONE); tmpParent.addView(mActivityView, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } - try { - mActivityView.release(); - } catch (IllegalStateException ex) { - Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex); - } + + mActivityView.release(); ((ViewGroup) mActivityView.getParent()).removeView(mActivityView); mActivityView = null; } @Override - public void setPosition(int x, int y) { + public void setPosition(float x, float y) { setPositionX(x); setPositionY(y); } @Override - public void setPositionX(int x) { + public void setPositionX(float x) { setTranslationX(x); } @Override - public void setPositionY(int y) { + public void setPositionY(float y) { setTranslationY(y); } @Override - public Point getPosition() { - return new Point((int) getTranslationX(), (int) getTranslationY()); + public PointF getPosition() { + return new PointF(getTranslationX(), getTranslationY()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java new file mode 100644 index 000000000000..f3ca9386c312 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.graphics.PointF; +import android.view.View; +import android.view.WindowInsets; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their expanded state, or animating to/from the + * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be + * dismissed. + */ +public class ExpandedAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + + /** + * The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack} + * and used to return to stack form in {@link #collapseBackToStack}. + */ + private PointF mExpandedFrom; + + /** Horizontal offset between bubbles, which we need to know to re-stack them. */ + private float mStackOffsetPx; + /** Spacing between bubbles in the expanded state. */ + private float mBubblePaddingPx; + /** Size of each bubble. */ + private float mBubbleSizePx; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + mStackOffsetPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_stack_offset); + mBubblePaddingPx = layout.getResources().getDimensionPixelSize( + R.dimen.bubble_padding); + mBubbleSizePx = layout.getResources().getDimensionPixelSize( + R.dimen.individual_bubble_size); + } + + /** + * Animates expanding the bubbles into a row along the top of the screen. + * + * @return The y-value to which the bubbles were expanded, in case that's useful. + */ + public float expandFromStack(PointF expandedFrom, Runnable after) { + mExpandedFrom = expandedFrom; + + // How much to translate the next bubble, so that it is not overlapping the previous one. + float translateNextBubbleXBy = mBubblePaddingPx; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY()); + translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx; + } + + runAfterTranslationsEnd(after); + return getExpandedY(); + } + + /** Animate collapsing the bubbles back to their stacked position. */ + public void collapseBackToStack(Runnable after) { + // Stack to the left if we're going to the left, or right if not. + final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1; + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.animatePositionForChildAtIndex( + i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y); + } + + runAfterTranslationsEnd(after); + } + + /** The Y value of the row of expanded bubbles. */ + private float getExpandedY() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + if (insets != null) { + return mBubblePaddingPx + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout().getSafeInsetTop()); + } + + return mBubblePaddingPx; + } + + /** Runs the given Runnable after all translation-related animations have ended. */ + private void runAfterTranslationsEnd(Runnable after) { + DynamicAnimation.OnAnimationEndListener allEndedListener = + (animation, canceled, value, velocity) -> { + if (!mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y)) { + after.run(); + } + }; + + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y); + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return 0; + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); + } + + @Override + void onChildAdded(View child, int index) { + // TODO: Animate the new bubble into the row, and push the other bubbles out of the way. + child.setTranslationY(getExpandedY()); + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // TODO: Animate the bubble out, and pull the other bubbles into its position. + actuallyRemove.run(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java new file mode 100644 index 000000000000..4e0abc8009b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +/** + * End listener that removes itself from its animation when called for the first time. Useful since + * anonymous OnAnimationEndListener instances can't pass themselves to + * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass + * implementation. + */ +public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener { + + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + animation.removeEndListener(this); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java new file mode 100644 index 000000000000..1ced3a4daac6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +/** + * Layout that constructs physics-based animations for each of its children, which behave according + * to settings provided by a {@link PhysicsAnimationController} instance. + * + * See physics-animation-layout.md. + */ +public class PhysicsAnimationLayout extends FrameLayout { + private static final String TAG = "Bubbs.PAL"; + + /** + * Controls the construction, configuration, and use of the physics animations supplied by this + * layout. + */ + abstract static class PhysicsAnimationController { + + /** + * Constant to return from {@link #getNextAnimationInChain} if the animation should not be + * chained at all. + */ + protected static final int NONE = -1; + + /** Set of properties for which the layout should construct physics animations. */ + abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties(); + + /** + * Returns the index of the next animation after the given index in the animation chain, or + * {@link #NONE} if it should not be chained, or if the chain should end at the given index. + * + * If a next index is returned, an update listener will be added to the animation at the + * given index that dispatches value updates to the animation at the next index. This + * creates a 'following' effect. + * + * Typical implementations of this method will return either index + 1, or index - 1, to + * create forward or backward chains between adjacent child views, but this is not required. + */ + abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index); + + /** + * Offsets to be added to the value that chained animations of the given property dispatch + * to subsequent child animations. + * + * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles + * stack off to the left or right side slightly. + */ + abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property); + + /** + * Returns the SpringForce to be used for the given child view's property animation. Despite + * these usually being similar or identical across properties and views, {@link SpringForce} + * also contains the SpringAnimation's final position, so we have to construct a new one for + * each animation rather than using a constant. + */ + abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view); + + /** + * Called when a new child is added at the specified index. Controllers can use this + * opportunity to animate in the new view. + */ + abstract void onChildAdded(View child, int index); + + /** + * Called when a child is to be removed from the layout. Controllers can use this + * opportunity to animate out the new view before calling the provided callback to actually + * remove it. + * + * Controllers should be careful to ensure that actuallyRemove is called on all code paths + * or child views will never be removed. + */ + abstract void onChildToBeRemoved(View child, int index, Runnable actuallyRemove); + + protected PhysicsAnimationLayout mLayout; + + PhysicsAnimationController() { } + + protected void setLayout(PhysicsAnimationLayout layout) { + this.mLayout = layout; + } + + protected PhysicsAnimationLayout getLayout() { + return mLayout; + } + } + + /** + * End listeners that are called when every child's animation of the given property has + * finished. + */ + protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener> + mEndListenerForProperty = new HashMap<>(); + + /** + * List of views that were passed to removeView, but are currently being animated out. These + * views will be actually removed by the controller (via super.removeView) once they're done + * animating out. + */ + private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>(); + + /** The currently active animation controller. */ + private PhysicsAnimationController mController; + + /** + * The maximum number of children to render and animate at a time. See + * {@link #setMaxRenderedChildren}. + */ + private int mMaxRenderedChildren = 5; + + public PhysicsAnimationLayout(Context context) { + super(context); + } + + /** + * The maximum number of children to render and animate at a time. Any child views added beyond + * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view, + * the corresponding property will be set with no animation. + */ + public void setMaxRenderedChildren(int max) { + this.mMaxRenderedChildren = max; + } + + /** + * Sets the animation controller and constructs or reconfigures the layout's physics animations + * to meet the controller's specifications. + */ + public void setController(PhysicsAnimationController controller) { + cancelAllAnimations(); + mEndListenerForProperty.clear(); + + this.mController = controller; + mController.setLayout(this); + + // Set up animations for this controller's animated properties. + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationsForProperty(property); + } + } + + /** + * Sets an end listener that will be called when all child animations for a given property have + * stopped running. + */ + public void setEndListenerForProperty( + DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + mEndListenerForProperty.put(property, listener); + } + + /** + * Removes the end listener that would have been called when all child animations for a given + * property stopped running. + */ + public void removeEndListenerForProperty(DynamicAnimation.ViewProperty property) { + mEndListenerForProperty.remove(property); + } + + /** + * Returns the index of the view that precedes the given index, ignoring views that were passed + * to removeView, but are currently being animated out before actually being removed. + * + * @return index of the preceding view, or -1 if there are none. + */ + public int getPrecedingNonRemovedViewIndex(int index) { + for (int i = index + 1; i < getChildCount(); i++) { + View precedingView = getChildAt(i); + if (!mViewsToBeActuallyRemoved.contains(precedingView)) { + return i; + } + } + + return -1; + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + super.addView(child, index, params); + setChildrenVisibility(); + + // Set up animations for the new view, if the controller is set. If it isn't set, we'll be + // setting up animations for all children when setController is called. + if (mController != null) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationForChild(property, child, index); + } + + mController.onChildAdded(child, index); + } + } + + @Override + public void removeView(View view) { + removeViewAndThen(view, /* callback */ null); + } + + /** + * Let the controller know that this view should be removed, and then call the callback once the + * controller has finished any removal animations and the view has actually been removed. + */ + public void removeViewAndThen(View view, Runnable callback) { + if (mController != null) { + final int index = indexOfChild(view); + // Remove the view only if it exists in this layout, and we're not already working on + // animating its removal. + if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) { + mViewsToBeActuallyRemoved.add(view); + setChildrenVisibility(); + + // Tell the controller to animate this view out, and call the callback when it wants + // to actually remove the view. + mController.onChildToBeRemoved(view, index, () -> { + removeViewImmediateAndThen(view, callback); + mViewsToBeActuallyRemoved.remove(view); + }); + } + } else { + // Without a controller, nobody will animate this view out, so it gets an unceremonious + // departure. + removeViewImmediateAndThen(view, callback); + } + } + + /** Checks whether any animations of the given properties are still running. */ + public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) { + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : properties) { + if (getAnimationAtIndex(property, i).isRunning()) { + return true; + } + } + } + + return false; + } + + /** Cancels all animations that are running on all child views, for all properties. */ + public void cancelAllAnimations() { + if (mController == null) { + return; + } + + for (int i = 0; i < getChildCount(); i++) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + getAnimationAtIndex(property, i).cancel(); + } + } + } + + /** + * Animates the property of the child at the given index to the given value, then runs the + * callback provided when the animation ends. + */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + float startVel, + Runnable after) { + if (index < getChildCount()) { + final SpringAnimation animation = getAnimationAtIndex(property, index); + if (after != null) { + animation.addEndListener(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + after.run(); + } + }); + } + + if (startVel != Float.MAX_VALUE) { + animation.setStartVelocity(startVel); + } + + animation.animateToFinalPosition(value); + } + } + + /** Shortcut to animate a value with a callback, but no start velocity. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + Runnable after) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, after); + } + + /** Shortcut to animate a value with a start velocity, but no callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value, + float startVel) { + animateValueForChildAtIndex(property, index, value, startVel, /* callback */ null); + } + + /** Shortcut to animate a value without changing the velocity or providing a callback. */ + protected void animateValueForChildAtIndex( + DynamicAnimation.ViewProperty property, + int index, + float value) { + animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, /* callback */ null); + } + + /** Shortcut to animate a child view's TRANSLATION_X and TRANSLATION_Y values. */ + protected void animatePositionForChildAtIndex(int index, float x, float y) { + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, index, x); + animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, index, y); + } + + /** Whether the first child would be left of center if translated to the given x value. */ + protected boolean isFirstChildXLeftOfCenter(float x) { + if (getChildCount() > 0) { + return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2; + } else { + return false; // If there's no first child, really anything is correct, right? + } + } + + /** ViewProperty's toString is useless, this returns a readable name for debug logging. */ + protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return "TRANSLATION_X"; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return "TRANSLATION_Y"; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return "SCALE_X"; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return "SCALE_Y"; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return "ALPHA"; + } else { + return "Unknown animation property."; + } + } + + + /** Immediately removes the view, without notifying the controller, then runs the callback. */ + private void removeViewImmediateAndThen(View view, Runnable callback) { + super.removeView(view); + + if (callback != null) { + callback.run(); + } + + setChildrenVisibility(); + } + + /** + * Retrieves the animation of the given property from the view at the given index via the view + * tag system. + */ + private SpringAnimation getAnimationAtIndex( + DynamicAnimation.ViewProperty property, int index) { + return (SpringAnimation) getChildAt(index).getTag(getTagIdForProperty(property)); + } + + /** Sets up SpringAnimations of the given property for each child view in the layout. */ + private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) { + for (int i = 0; i < getChildCount(); i++) { + setUpAnimationForChild(property, getChildAt(i), i); + } + } + + /** Constructs a SpringAnimation of the given property for a child view. */ + private void setUpAnimationForChild( + DynamicAnimation.ViewProperty property, View child, int index) { + SpringAnimation newAnim = new SpringAnimation(child, property); + newAnim.addUpdateListener((animation, value, velocity) -> { + final int nextAnimInChain = + mController.getNextAnimationInChain(property, indexOfChild(child)); + if (nextAnimInChain == PhysicsAnimationController.NONE) { + return; + } + + final int animIndex = indexOfChild(child); + final float offset = + mController.getOffsetForChainedPropertyAnimation(property); + + // If this property's animations should be chained, then check to see if there is a + // subsequent animation within the rendering limit, and if so, tell it to animate to + // this animation's new value (plus the offset). + if (nextAnimInChain < Math.min( + getChildCount(), + mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) { + getAnimationAtIndex(property, animIndex + 1) + .animateToFinalPosition(value + offset); + } else if (nextAnimInChain < getChildCount()) { + // If the next child view is not rendered, update the property directly without + // animating it, so that the view is still in the correct state if it later + // becomes visible. + for (int i = nextAnimInChain; i < getChildCount(); i++) { + // 'value' here is the value of the last child within the rendering limit, + // not the first child's value - so we want to subtract the last child's + // index when calculating the offset. + property.setValue(getChildAt(i), value + offset * (i - animIndex)); + } + } + }); + + newAnim.setSpring(mController.getSpringForce(property, child)); + newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property)); + child.setTag(getTagIdForProperty(property), newAnim); + } + + /** Hides children beyond the max rendering count. */ + private void setChildrenVisibility() { + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).setVisibility( + // Ignore views that are animating out when calculating whether to hide the + // view. That is, if we're supposed to render 5 views, but 4 are animating out + // and will soon be removed, render up to 9 views temporarily. + i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size()) + ? View.VISIBLE + : View.GONE); + } + } + + /** Return a stable ID to use as a tag key for the given property's animations. */ + private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + return R.id.translation_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + return R.id.translation_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_X)) { + return R.id.scale_x_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.SCALE_Y)) { + return R.id.scale_y_dynamicanimation_tag; + } else if (property.equals(DynamicAnimation.ALPHA)) { + return R.id.alpha_dynamicanimation_tag; + } + + return -1; + } + + /** + * End listener that is added to each individual DynamicAnimation, which dispatches to a single + * listener when every other animation of the given property is no longer running. + * + * This is required since chained DynamicAnimations can stop and start again due to changes in + * upstream animations. This means that adding an end listener to just the last animation is not + * sufficient. By firing only when every other animation on the property has stopped running, we + * ensure that no animation will be restarted after the single end listener is called. + */ + protected class AllAnimationsForPropertyFinishedEndListener + implements DynamicAnimation.OnAnimationEndListener { + private DynamicAnimation.ViewProperty mProperty; + + AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) { + this.mProperty = property; + } + + @Override + public void onAnimationEnd( + DynamicAnimation anim, boolean canceled, float value, float velocity) { + if (!arePropertiesAnimating(mProperty)) { + if (mEndListenerForProperty.containsKey(mProperty)) { + mEndListenerForProperty.get(mProperty).onAnimationEnd(anim, canceled, value, + velocity); + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java new file mode 100644 index 000000000000..a113a630dfd8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FlingAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import com.google.android.collect.Sets; + +import java.util.HashMap; +import java.util.Set; + +/** + * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop + * each other with a slight offset to the left or right (depending on which side of the screen they + * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of + * the screen. + */ +public class StackAnimationController extends + PhysicsAnimationLayout.PhysicsAnimationController { + + private static final String TAG = "Bubbs.StackCtrl"; + + /** Scale factor to use initially for new bubbles being animated in. */ + private static final float ANIMATE_IN_STARTING_SCALE = 1.15f; + + /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */ + private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4; + + /** + * Values to use for the default {@link SpringForce} provided to the physics animation layout. + */ + private static final float DEFAULT_STIFFNESS = 2500f; + private static final float DEFAULT_BOUNCINESS = 0.85f; + + /** + * The canonical position of the stack. This is typically the position of the first bubble, but + * we need to keep track of it separately from the first bubble's translation in case there are + * no bubbles, or the first bubble was just added and being animated to its new position. + */ + private PointF mStackPosition = new PointF(); + + /** + * Animations on the stack position itself, which would have been started in + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to + * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect) + * to a legal position on the side of the screen. + */ + private HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mStackPositionAnimations = + new HashMap<>(); + + /** Horizontal offset of bubbles in the stack. */ + private float mStackOffset; + /** Diameter of the bubbles themselves. */ + private int mIndividualBubbleSize; + /** Size of spacing around the bubbles, separating it from the edge of the screen. */ + private int mBubblePadding; + /** How far offscreen the stack rests. */ + private int mBubbleOffscreen; + /** How far down the screen the stack starts, when there is no pre-existing location. */ + private int mStackStartingVerticalOffset; + + private Point mDisplaySize; + private RectF mAllowableStackPositionRegion; + + @Override + protected void setLayout(PhysicsAnimationLayout layout) { + super.setLayout(layout); + + Resources res = layout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); + mStackStartingVerticalOffset = + res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); + + mDisplaySize = new Point(); + WindowManager wm = + (WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getSize(mDisplaySize); + } + + /** + * Instantly move the first bubble to the given point, and animate the rest of the stack behind + * it with the 'following' effect. + */ + public void moveFirstBubbleWithStackFollowing(float x, float y) { + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x); + moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y); + } + + /** + * The position of the stack - typically the position of the first bubble; if no bubbles have + * been added yet, it will be where the first bubble will go when added. + */ + public PointF getStackPosition() { + return mStackPosition; + } + + /** + * Flings the first bubble along the given property's axis, using the provided configuration + * values. When the animation ends - either by hitting the min/max, or by friction sufficiently + * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final + * position. + */ + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, + float vel, + float friction, + SpringForce spring, + Float finalPosition) { + Log.d(TAG, String.format("Flinging %s.", + PhysicsAnimationLayout.getReadablePropertyName(property))); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + final float currentValue = firstBubbleProperty.getValue(this); + final RectF bounds = getAllowableStackPositionRegion(); + final float min = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.left + : bounds.top; + final float max = + property.equals(DynamicAnimation.TRANSLATION_X) + ? bounds.right + : bounds.bottom; + + FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty); + flingAnimation.setFriction(friction) + .setStartVelocity(vel) + + // If the bubble's property value starts beyond the desired min/max, use that value + // instead so that the animation won't immediately end. If, for example, the user + // drags the bubbles into the navigation bar, but then flings them upward, we want + // the fling to occur despite temporarily having a value outside of the min/max. If + // the bubbles are out of bounds and flung even farther out of bounds, the fling + // animation will halt immediately and the SpringAnimation will take over, springing + // it in reverse to the (legal) final position. + .setMinValue(Math.min(currentValue, min)) + .setMaxValue(Math.max(currentValue, max)) + + .addEndListener((animation, canceled, endValue, endVelocity) -> { + if (!canceled) { + springFirstBubbleWithStackFollowing(property, spring, endVelocity, + finalPosition != null + ? finalPosition + : Math.max(min, Math.min(max, endValue))); + } + }); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, flingAnimation); + flingAnimation.start(); + } + + /** + * Cancel any stack position animations that were started by calling + * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end + * listeners. + */ + public void cancelStackPositionAnimations() { + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X); + cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y); + + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y); + } + + /** + * Returns the region within which the stack is allowed to rest. This goes slightly off the left + * and right sides of the screen, below the status bar/cutout and above the navigation bar. + * While the stack is not allowed to rest outside of these bounds, it can temporarily be + * animated or dragged beyond them. + */ + public RectF getAllowableStackPositionRegion() { + final WindowInsets insets = mLayout.getRootWindowInsets(); + mAllowableStackPositionRegion = new RectF(); + + if (insets != null) { + mAllowableStackPositionRegion.left = + -mBubbleOffscreen + - mBubblePadding + + Math.max( + insets.getSystemWindowInsetLeft(), + insets.getDisplayCutout().getSafeInsetLeft()); + mAllowableStackPositionRegion.right = + mLayout.getWidth() + - mIndividualBubbleSize + + mBubbleOffscreen + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetRight(), + insets.getDisplayCutout().getSafeInsetRight()); + + mAllowableStackPositionRegion.top = + mBubblePadding + + Math.max( + insets.getSystemWindowInsetTop(), + insets.getDisplayCutout().getSafeInsetTop()); + mAllowableStackPositionRegion.bottom = + mLayout.getHeight() + - mIndividualBubbleSize + - mBubblePadding + - Math.max( + insets.getSystemWindowInsetBottom(), + insets.getDisplayCutout().getSafeInsetBottom()); + } + + return mAllowableStackPositionRegion; + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, // For positioning. + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.ALPHA, // For fading in new bubbles. + DynamicAnimation.SCALE_X, // For 'popping in' new bubbles. + DynamicAnimation.SCALE_Y); + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + if (property.equals(DynamicAnimation.TRANSLATION_X) + || property.equals(DynamicAnimation.TRANSLATION_Y)) { + return index + 1; // Just chain them linearly. + } else { + return NONE; + } + } + + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + // Offset to the left if we're on the left, or the right otherwise. + return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x) + ? -mStackOffset : mStackOffset; + } else { + return 0f; + } + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce() + .setDampingRatio(DEFAULT_BOUNCINESS) + .setStiffness(DEFAULT_STIFFNESS); + } + + @Override + void onChildAdded(View child, int index) { + // If this is the first child added, position the stack in its starting position. + if (mLayout.getChildCount() == 1) { + moveStackToStartPosition(); + } + + if (mLayout.indexOfChild(child) == 0) { + child.setTranslationY(mStackPosition.y); + + // Pop in the new bubble. + child.setScaleX(ANIMATE_IN_STARTING_SCALE); + child.setScaleY(ANIMATE_IN_STARTING_SCALE); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f); + mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f); + + // Fade in the new bubble. + child.setAlpha(0); + mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f); + + // Start the new bubble 4x the normal offset distance in the opposite direction. We'll + // animate in from this position. Since the animations are chained, when the new bubble + // flies in from the side, it will push the other ones out of the way. + float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset)); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x); + } + } + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + // Animate the child out, actually removing it once its alpha is zero. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.ALPHA, index, 0f, () -> { + actuallyRemove.run(); + }); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE); + + final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount(); + if (hasPrecedingChild) { + final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index); + if (precedingViewIndex >= 0) { + final float offsetX = + getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + mLayout.animatePositionForChildAtIndex( + precedingViewIndex, + mStackPosition.x + (index * offsetX), + mStackPosition.y); + } + } + } + + /** Moves the stack, without any animation, to the starting position. */ + private void moveStackToStartPosition() { + mLayout.post(() -> setStackPosition( + getAllowableStackPositionRegion().right, + getAllowableStackPositionRegion().top + mStackStartingVerticalOffset)); + } + + /** + * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent + * bubbles to animate 'following' to the new location. + */ + private void moveFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float value) { + + // Update the canonical stack position. + if (property.equals(DynamicAnimation.TRANSLATION_X)) { + mStackPosition.x = value; + } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { + mStackPosition.y = value; + } + + if (mLayout.getChildCount() > 0) { + property.setValue(mLayout.getChildAt(0), value); + mLayout.animateValueForChildAtIndex( + property, + /* index */ 1, + value + getOffsetForChainedPropertyAnimation(property)); + } + } + + /** Moves the stack to a position instantly, with no animation. */ + private void setStackPosition(float x, float y) { + Log.d(TAG, String.format("Setting position to (%f, %f).", x, y)); + mStackPosition.set(x, y); + + cancelStackPositionAnimations(); + + // Since we're not using the chained animations, apply the offsets manually. + final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); + final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y); + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.getChildAt(i).setTranslationX(x + (i * xOffset)); + mLayout.getChildAt(i).setTranslationY(y + (i * yOffset)); + } + } + + /** + * Springs the first bubble to the given final position, with the rest of the stack 'following'. + */ + private void springFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, SpringForce spring, + float vel, float finalPosition) { + + Log.d(TAG, String.format("Springing %s to final position %f.", + PhysicsAnimationLayout.getReadablePropertyName(property), + finalPosition)); + + StackPositionProperty firstBubbleProperty = new StackPositionProperty(property); + SpringAnimation springAnimation = + new SpringAnimation(this, firstBubbleProperty) + .setSpring(spring) + .setStartVelocity(vel); + + cancelStackPositionAnimation(property); + mStackPositionAnimations.put(property, springAnimation); + springAnimation.animateToFinalPosition(finalPosition); + } + + /** + * Cancels any outstanding first bubble property animations that are running. This does not + * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only + * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and + * {@link #flingThenSpringFirstBubbleWithStackFollowing}. + */ + private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) { + if (mStackPositionAnimations.containsKey(property)) { + mStackPositionAnimations.get(property).cancel(); + } + } + + /** + * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's + * translation and animate the rest of the stack with it. A DynamicAnimation can animate this + * property directly to move the first bubble and cause the stack to 'follow' to the new + * location. + * + * This could also be achieved by simply animating the first bubble view and adding an update + * listener to dispatch movement to the rest of the stack. However, this would require + * duplication of logic in that update handler - it's simpler to keep all logic contained in the + * {@link #moveFirstBubbleWithStackFollowing} method. + */ + private class StackPositionProperty + extends FloatPropertyCompat<StackAnimationController> { + private final DynamicAnimation.ViewProperty mProperty; + + private StackPositionProperty(DynamicAnimation.ViewProperty property) { + super(property.toString()); + mProperty = property; + } + + @Override + public float getValue(StackAnimationController controller) { + return mProperty.getValue(mLayout.getChildAt(0)); + } + + @Override + public void setValue(StackAnimationController controller, float value) { + moveFirstBubbleWithStackFollowing(mProperty, value); + } + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 7b18fad0e105..f5ac0d39d61f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1090,9 +1090,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + protected int getActionLayoutId(Context context) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + return com.android.systemui.R.layout.global_actions_grid_item; + } + return com.android.systemui.R.layout.global_actions_item; + } + public View create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - View v = inflater.inflate(com.android.systemui.R.layout.global_actions_item, parent, + View v = inflater.inflate(getActionLayoutId(context), parent, false); ImageView icon = (ImageView) v.findViewById(R.id.icon); @@ -1498,7 +1505,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, window.setBackgroundDrawable(mGradientDrawable); window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - setContentView(com.android.systemui.R.layout.global_actions_wrapped); + + setContentView(getGlobalActionsLayoutId(context)); mGlobalActionsLayout = (MultiListLayout) findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); @@ -1515,6 +1523,13 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, setTitle(R.string.global_actions); } + private int getGlobalActionsLayoutId(Context context) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + return com.android.systemui.R.layout.global_actions_grid; + } + return com.android.systemui.R.layout.global_actions_wrapped; + } + private void updateList() { mGlobalActionsLayout.removeAllItems(); ArrayList<Action> separatedActions = diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java new file mode 100644 index 000000000000..0e49b5f3cd2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.HardwareBgDrawable; +import com.android.systemui.MultiListLayout; + +/** + * Grid-based implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsGridLayout extends MultiListLayout { + + boolean mBackgroundsSet; + + public GlobalActionsGridLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void setBackgrounds() { + HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext()); + HardwareBgDrawable separatedViewBackground = new HardwareBgDrawable(true, true, + getContext()); + getListView().setBackground(listBackground); + getSeparatedView().setBackground(separatedViewBackground); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // backgrounds set only once, the first time onMeasure is called after inflation + if (getListView() != null && !mBackgroundsSet) { + setBackgrounds(); + mBackgroundsSet = true; + } + } + + @Override + public void setExpectedListItemCount(int count) { + mExpectedListItemCount = count; + getListView().setExpectedCount(count); + } + + @Override + protected ViewGroup getSeparatedView() { + return findViewById(com.android.systemui.R.id.separated_button); + } + + @Override + protected ListGridLayout getListView() { + return findViewById(android.R.id.list); + } + + @Override + public void removeAllItems() { + ViewGroup separatedList = getSeparatedView(); + ListGridLayout list = getListView(); + if (separatedList != null) { + separatedList.removeAllViews(); + } + if (list != null) { + list.removeAllItems(); + } + } + + @Override + public ViewGroup getParentView(boolean separated, int index) { + if (separated) { + return getSeparatedView(); + } else { + return getListView().getParentView(index); + } + } + + /** + * Not used in this implementation of the Global Actions Menu, but necessary for some others. + */ + @Override + public void setDivisionView(View v) { + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java new file mode 100644 index 000000000000..37755155751f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +/** + * Layout which uses nested LinearLayouts to create a grid with the following behavior: + * + * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item + * count. + * * Display and hide sub-lists as needed, depending on the expected item count. + * * Favor bias toward having more rows or columns depending on the orientation of the device + * (TODO(123344999): Implement this, currently always favors adding more rows.) + * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows + * or columns depending on the orientation of the device. + * (TODO(123344999): Implement this, currently always columns.) + * + * While we could implement this behavior with a GridLayout, it would take significantly more + * time and effort, and would require more substantial refactoring of the existing code in + * GlobalActionsDialog, since it would require manipulation of the child items themselves. + * + */ + +public class ListGridLayout extends LinearLayout { + private int mExpectedCount; + private int mRows; + private int mColumns; + + public ListGridLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Remove all items from this grid. + */ + public void removeAllItems() { + for (int i = 0; i < getChildCount(); i++) { + ViewGroup subList = (ViewGroup) getChildAt(i); + if (subList != null) { + subList.removeAllViews(); + } + } + } + + /** + * Get the parent view associated with the item which should be placed at the given position. + */ + public ViewGroup getParentView(int index) { + ViewGroup firstParent = (ViewGroup) getChildAt(0); + if (mRows == 0) { + return firstParent; + } + int column = (int) Math.floor(index / mRows); + ViewGroup parent = (ViewGroup) getChildAt(column); + return parent != null ? parent : firstParent; + } + + /** + * Sets the expected number of items that this grid will be responsible for rendering. + */ + public void setExpectedCount(int count) { + mExpectedCount = count; + mRows = getRowCount(); + mColumns = getColumnCount(); + + for (int i = 0; i < getChildCount(); i++) { + if (i <= mColumns) { + setSublistVisibility(i, true); + } else { + setSublistVisibility(i, false); + } + } + + } + + private void setSublistVisibility(int index, boolean visible) { + View subList = getChildAt(index); + Log.d("ListGrid", "index: " + index + ", visibility: " + visible); + if (subList != null) { + subList.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + private int getRowCount() { + return (int) Math.ceil(Math.sqrt(mExpectedCount)); + } + + private int getColumnCount() { + return (int) Math.round(Math.sqrt(mExpectedCount)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index b218e80693b1..2339fae2d1ee 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -30,8 +30,12 @@ import com.android.systemui.Dependency import com.android.systemui.appops.AppOpItem import com.android.systemui.appops.AppOpsController import com.android.systemui.R +import java.lang.ref.WeakReference +import javax.inject.Inject +import javax.inject.Singleton -class PrivacyItemController(val context: Context, val callback: Callback) { +@Singleton +class PrivacyItemController @Inject constructor(val context: Context) { companion object { val OPS = intArrayOf(AppOpsManager.OP_CAMERA, @@ -56,9 +60,10 @@ class PrivacyItemController(val context: Context, val callback: Callback) { private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER) private var listening = false val systemApp = PrivacyApplication(context.getString(R.string.device_services), context) + private val callbacks = mutableListOf<WeakReference<Callback>>() private val notifyChanges = Runnable { - callback.privacyChanged(privacyList) + callbacks.forEach { it.get()?.privacyChanged(privacyList) } } private val updateListAndNotifyChanges = Runnable { @@ -88,8 +93,8 @@ class PrivacyItemController(val context: Context, val callback: Callback) { registerReceiver() } - init { - registerReceiver() + private fun unregisterReceiver() { + context.unregisterReceiver(userSwitcherReceiver) } private fun registerReceiver() { @@ -108,17 +113,41 @@ class PrivacyItemController(val context: Context, val callback: Callback) { bgHandler.post(updateListAndNotifyChanges) } - fun setListening(listen: Boolean) { + @VisibleForTesting + internal fun setListening(listen: Boolean) { if (listening == listen) return listening = listen if (listening) { appOpsController.addCallback(OPS, cb) + registerReceiver() update(true) } else { appOpsController.removeCallback(OPS, cb) + unregisterReceiver() } } + private fun addCallback(callback: WeakReference<Callback>) { + callbacks.add(callback) + if (callbacks.isNotEmpty() && !listening) setListening(true) + // Notify this callback if we didn't set to listening + else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList)) + } + + private fun removeCallback(callback: WeakReference<Callback>) { + // Removes also if the callback is null + callbacks.removeIf { it.get()?.equals(callback.get()) ?: true } + if (callbacks.isEmpty()) setListening(false) + } + + fun addCallback(callback: Callback) { + addCallback(WeakReference(callback)) + } + + fun removeCallback(callback: Callback) { + removeCallback(WeakReference(callback)) + } + private fun updatePrivacyList() { privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) } .mapNotNull { toPrivacyItem(it) }.distinct() @@ -149,4 +178,13 @@ class PrivacyItemController(val context: Context, val callback: Callback) { } } } + + private class NotifyChangesToCallback( + private val callback: Callback?, + private val list: List<PrivacyItem> + ) : Runnable { + override fun run() { + callback?.privacyChanged(list) + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index e63f88a898cc..c0ed4b97eaff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -30,14 +30,18 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.Bundle; import android.os.UserManager; +import android.telephony.SubscriptionManager; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; @@ -45,7 +49,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.keyguard.CarrierText; +import com.android.keyguard.CarrierTextController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.settingslib.drawable.UserIconDrawable; @@ -68,7 +72,11 @@ import javax.inject.Inject; import javax.inject.Named; public class QSFooterImpl extends FrameLayout implements QSFooter, - OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback { + OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback, + CarrierTextController.CarrierTextCallback { + + private static final int SIM_SLOTS = 2; + private static final String TAG = "QSFooterImpl"; private final ActivityStarter mActivityStarter; private final UserInfoController mUserInfoController; @@ -77,7 +85,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private SettingsButton mSettingsButton; protected View mSettingsContainer; private PageIndicator mPageIndicator; - private CarrierText mCarrierText; private boolean mQsDisabled; private QSPanel mQsPanel; @@ -99,12 +106,20 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private View mActionsContainer; private View mDragHandle; - private View mMobileGroup; - private ImageView mMobileSignal; - private ImageView mMobileRoaming; + + private View mCarrierDivider; + private ViewGroup mMobileFooter; + private View[] mMobileGroups = new View[SIM_SLOTS]; + private ViewGroup[] mCarrierGroups = new ViewGroup[SIM_SLOTS]; + private TextView[] mCarrierTexts = new TextView[SIM_SLOTS]; + private ImageView[] mMobileSignals = new ImageView[SIM_SLOTS]; + private ImageView[] mMobileRoamings = new ImageView[SIM_SLOTS]; + private final CellSignalState[] mInfos = + new CellSignalState[]{new CellSignalState(), new CellSignalState()}; + private final int mColorForeground; - private final CellSignalState mInfo = new CellSignalState(); private OnClickListener mExpandClickListener; + private CarrierTextController mCarrierTextController; @Inject public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, @@ -134,10 +149,20 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mSettingsContainer = findViewById(R.id.settings_button_container); mSettingsButton.setOnClickListener(this); - mMobileGroup = findViewById(R.id.mobile_combo); - mMobileSignal = findViewById(R.id.mobile_signal); - mMobileRoaming = findViewById(R.id.mobile_roaming); - mCarrierText = findViewById(R.id.qs_carrier_text); + mMobileFooter = findViewById(R.id.qs_mobile); + mCarrierGroups[0] = findViewById(R.id.carrier1); + mCarrierGroups[1] = findViewById(R.id.carrier2); + + for (int i = 0; i < SIM_SLOTS; i++) { + mMobileGroups[i] = mCarrierGroups[i].findViewById(R.id.mobile_combo); + mMobileSignals[i] = mCarrierGroups[i].findViewById(R.id.mobile_signal); + mMobileRoamings[i] = mCarrierGroups[i].findViewById(R.id.mobile_roaming); + mCarrierTexts[i] = mCarrierGroups[i].findViewById(R.id.qs_carrier_text); + } + mCarrierDivider = findViewById(R.id.qs_carrier_divider); + CharSequence separator = mContext.getString( + com.android.internal.R.string.kg_text_message_separator); + mCarrierTextController = new CarrierTextController(mContext, separator, false, false); mMultiUserSwitch = findViewById(R.id.multi_user_switch); mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); @@ -204,8 +229,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, private TouchAnimator createFooterAnimator() { return new TouchAnimator.Builder() .addFloat(mDivider, "alpha", 0, 1) - .addFloat(mCarrierText, "alpha", 0, 0, 1) - .addFloat(mMobileGroup, "alpha", 0, 1) + .addFloat(mMobileFooter, "alpha", 0, 0, 1) + .addFloat(mCarrierDivider, "alpha", 0, 1) .addFloat(mActionsContainer, "alpha", 0, 1) .addFloat(mDragHandle, "alpha", 1, 0, 0) .addFloat(mPageIndicator, "alpha", 0, 1) @@ -332,10 +357,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mNetworkController.addEmergencyListener(this); mNetworkController.addCallback(this); } + mCarrierTextController.setListening(this); } else { mUserInfoController.removeCallback(this); mNetworkController.removeEmergencyListener(this); mNetworkController.removeCallback(this); + mCarrierTextController.setListening(null); } } @@ -358,7 +385,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, if (v == mSettingsButton) { if (!mDeviceProvisionedController.isCurrentUserSetup()) { // If user isn't setup just unlock the device and dump them back at SUW. - mActivityStarter.postQSRunnableDismissingKeyguard(() -> { }); + mActivityStarter.postQSRunnableDismissingKeyguard(() -> { + }); return; } MetricsLogger.action(mContext, @@ -415,32 +443,64 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, } private void handleUpdateState() { - mMobileGroup.setVisibility(mInfo.visible ? View.VISIBLE : View.GONE); - if (mInfo.visible) { - mMobileRoaming.setVisibility(mInfo.roaming ? View.VISIBLE : View.GONE); - mMobileRoaming.setImageTintList(ColorStateList.valueOf(mColorForeground)); - SignalDrawable d = new SignalDrawable(mContext); - d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground)); - mMobileSignal.setImageDrawable(d); - mMobileSignal.setImageLevel(mInfo.mobileSignalIconId); - - StringBuilder contentDescription = new StringBuilder(); - if (mInfo.contentDescription != null) { - contentDescription.append(mInfo.contentDescription).append(", "); + for (int i = 0; i < SIM_SLOTS; i++) { + mMobileGroups[i].setVisibility(mInfos[i].visible ? View.VISIBLE : View.GONE); + if (mInfos[i].visible) { + mMobileRoamings[i].setVisibility(mInfos[i].roaming ? View.VISIBLE : View.GONE); + mMobileRoamings[i].setImageTintList(ColorStateList.valueOf(mColorForeground)); + SignalDrawable d = new SignalDrawable(mContext); + d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground)); + mMobileSignals[i].setImageDrawable(d); + mMobileSignals[i].setImageLevel(mInfos[i].mobileSignalIconId); + + StringBuilder contentDescription = new StringBuilder(); + if (mInfos[i].contentDescription != null) { + contentDescription.append(mInfos[i].contentDescription).append(", "); + } + if (mInfos[i].roaming) { + contentDescription + .append(mContext.getString(R.string.data_connection_roaming)) + .append(", "); + } + // TODO: show mobile data off/no internet text for 5 seconds before carrier text + if (TextUtils.equals(mInfos[i].typeContentDescription, + mContext.getString(R.string.data_connection_no_internet)) + || TextUtils.equals(mInfos[i].typeContentDescription, + mContext.getString(R.string.cell_data_off_content_description))) { + contentDescription.append(mInfos[i].typeContentDescription); + } + mMobileSignals[i].setContentDescription(contentDescription); } - if (mInfo.roaming) { - contentDescription - .append(mContext.getString(R.string.data_connection_roaming)) - .append(", "); + } + mCarrierDivider.setVisibility( + mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE); + } + + @Override + public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + if (info.anySimReady) { + boolean[] slotSeen = new boolean[SIM_SLOTS]; + for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) { + int slot = SubscriptionManager.getSlotIndex(info.subscriptionIds[i]); + mInfos[slot].visible = true; + slotSeen[slot] = true; + mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim()); + mCarrierGroups[slot].setVisibility(View.VISIBLE); } - // TODO: show mobile data off/no internet text for 5 seconds before carrier text - if (TextUtils.equals(mInfo.typeContentDescription, - mContext.getString(R.string.data_connection_no_internet)) - || TextUtils.equals(mInfo.typeContentDescription, - mContext.getString(R.string.cell_data_off_content_description))) { - contentDescription.append(mInfo.typeContentDescription); + for (int i = 0; i < SIM_SLOTS; i++) { + if (!slotSeen[i]) { + mInfos[i].visible = false; + mCarrierGroups[i].setVisibility(View.GONE); + } } - mMobileSignal.setContentDescription(contentDescription); + handleUpdateState(); + } else { + mInfos[0].visible = false; + mInfos[1].visible = false; + mCarrierTexts[0].setText(info.carrierText); + mCarrierGroups[0].setVisibility(View.VISIBLE); + mCarrierGroups[1].setVisibility(View.GONE); + handleUpdateState(); } } @@ -450,18 +510,23 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, String description, boolean isWide, int subId, boolean roaming) { - mInfo.visible = statusIcon.visible; - mInfo.mobileSignalIconId = statusIcon.icon; - mInfo.contentDescription = statusIcon.contentDescription; - mInfo.typeContentDescription = typeContentDescription; - mInfo.roaming = roaming; + int slotIndex = SubscriptionManager.getSlotIndex(subId); + if (slotIndex >= SIM_SLOTS) { + Log.e(TAG, "setMobileDataIndicators - slot: " + slotIndex); + } + mInfos[slotIndex].visible = statusIcon.visible; + mInfos[slotIndex].mobileSignalIconId = statusIcon.icon; + mInfos[slotIndex].contentDescription = statusIcon.contentDescription; + mInfos[slotIndex].typeContentDescription = typeContentDescription; + mInfos[slotIndex].roaming = roaming; handleUpdateState(); } @Override public void setNoSims(boolean hasNoSims, boolean simDetected) { if (hasNoSims) { - mInfo.visible = false; + mInfos[0].visible = false; + mInfos[1].visible = false; } handleUpdateState(); } @@ -473,4 +538,38 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, String typeContentDescription; boolean roaming; } + + + /** + * TextView that changes its ellipsize value with its visibility. + */ + public static class QSCarrierText extends TextView { + public QSCarrierText(Context context) { + super(context); + } + + public QSCarrierText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + // Only show marquee when visible + if (visibility == VISIBLE) { + setEllipsize(TextUtils.TruncateAt.MARQUEE); + } else { + setEllipsize(TextUtils.TruncateAt.END); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 75ab5dfd2977..2d64ecd99c88 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -180,14 +180,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, NextAlarmController nextAlarmController, ZenModeController zenModeController, BatteryController batteryController, StatusBarIconController statusBarIconController, - ActivityStarter activityStarter) { + ActivityStarter activityStarter, PrivacyItemController privacyItemController) { super(context, attrs); mAlarmController = nextAlarmController; mZenController = zenModeController; mBatteryController = batteryController; mStatusBarIconController = statusBarIconController; mActivityStarter = activityStarter; - mPrivacyItemController = new PrivacyItemController(context, mPICCallback); + mPrivacyItemController = privacyItemController; mShownCount = getStoredShownCount(); } @@ -512,7 +512,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements return; } mHeaderQsPanel.setListening(listening); - mPrivacyItemController.setListening(listening); mListening = listening; if (listening) { @@ -520,9 +519,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements mAlarmController.addCallback(this); mContext.registerReceiver(mRingerReceiver, new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); + mPrivacyItemController.addCallback(mPICCallback); } else { mZenController.removeCallback(this); mAlarmController.removeCallback(this); + mPrivacyItemController.removeCallback(mPICCallback); mContext.unregisterReceiver(mRingerReceiver); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index b04132d09b7c..43ce0b50e03d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -82,7 +82,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> if ("1".equals(Settings.Global.getString(mContext.getContentResolver(), Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE)) && mController.getAutoModeRaw() == -1) { - mController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM); + mController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME); Log.i("NightDisplayTile", "Enrolled in forced night display auto mode"); } @@ -127,7 +127,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> @Nullable private String getSecondaryLabel(boolean isNightLightActivated) { switch(mController.getAutoMode()) { - case ColorDisplayController.AUTO_MODE_TWILIGHT: + case ColorDisplayManager.AUTO_MODE_TWILIGHT: // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset. return isNightLightActivated @@ -136,7 +136,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> : mContext.getString( R.string.quick_settings_night_secondary_label_on_at_sunset); - case ColorDisplayController.AUTO_MODE_CUSTOM: + case ColorDisplayManager.AUTO_MODE_CUSTOM_TIME: // User-specified time, approximated to the nearest hour. final @StringRes int toggleTimeStringRes; final LocalTime toggleTime; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index 6c3e504da8bd..04534ba06d7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import android.view.View; import com.android.systemui.Interpolators; +import com.android.systemui.R; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; /** @@ -92,9 +93,15 @@ public class CrossFadeHelper { private static void updateLayerType(View view, float alpha) { if (view.hasOverlappingRendering() && alpha > 0.0f && alpha < 1.0f) { - view.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) { - view.setLayerType(View.LAYER_TYPE_NONE, null); + if (view.getLayerType() != View.LAYER_TYPE_HARDWARE) { + view.setLayerType(View.LAYER_TYPE_HARDWARE, null); + view.setTag(R.id.cross_fade_layer_type_changed_tag, true); + } + } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE + && view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) { + if (view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) { + view.setLayerType(View.LAYER_TYPE_NONE, null); + } } } @@ -114,7 +121,7 @@ public class CrossFadeHelper { .setStartDelay(delay) .setInterpolator(Interpolators.ALPHA_IN) .withEndAction(null); - if (view.hasOverlappingRendering()) { + if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) { view.animate().withLayer(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt new file mode 100644 index 000000000000..494473242a90 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar + +import android.annotation.ColorInt +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Point +import android.graphics.Rect +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.ScriptIntrinsicBlur +import android.util.MathUtils +import com.android.internal.graphics.ColorUtils + +import javax.inject.Inject +import javax.inject.Singleton + +private const val COLOR_ALPHA = (255 * 0.7f).toInt() +private const val BLUR_RADIUS = 25f +private const val DOWNSAMPLE = 6 + +@Singleton +class MediaArtworkProcessor @Inject constructor() { + + private val mTmpSize = Point() + private var mArtworkCache: Bitmap? = null + + fun processArtwork(context: Context, artwork: Bitmap, @ColorInt color: Int): Bitmap { + if (mArtworkCache != null) { + return mArtworkCache!! + } + + context.display.getSize(mTmpSize) + val renderScript = RenderScript.create(context) + val rect = Rect(0, 0,artwork.width, artwork.height) + MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) + val inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), + true /* filter */) + val input = Allocation.createFromBitmap(renderScript, inBitmap, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) + val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height, + Bitmap.Config.ARGB_8888) + val output = Allocation.createFromBitmap(renderScript, outBitmap) + val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) + blur.setRadius(BLUR_RADIUS) + blur.setInput(input) + blur.forEach(output) + output.copyTo(outBitmap) + + input.destroy() + output.destroy() + inBitmap.recycle() + + val canvas = Canvas(outBitmap) + canvas.drawColor(ColorUtils.setAlphaComponent(color, COLOR_ALPHA)) + return outBitmap + } + + fun clearCache() { + mArtworkCache?.recycle() + mArtworkCache = null + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index f46ded4d61d8..c25b7cfd804c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -39,6 +39,9 @@ public interface NotificationLockscreenUserManager { boolean isCurrentProfile(int userId); + /** Adds a listener to be notified when the current user changes. */ + void addUserChangedListener(UserChangedListener listener); + void destroy(); SparseArray<UserInfo> getCurrentProfiles(); @@ -58,4 +61,9 @@ public interface NotificationLockscreenUserManager { boolean needsRedaction(NotificationEntry entry); boolean userAllowsPrivateNotificationsInPublic(int currentUserId); + + /** Notified when the current user changes. */ + interface UserChangedListener { + void onUserChanged(int userId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index d2ce31d75648..4f9d4282dae8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -55,6 +55,8 @@ import com.android.systemui.statusbar.policy.KeyguardMonitor; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * Handles keeping track of the current user, profiles, and various things related to hiding @@ -78,6 +80,7 @@ public class NotificationLockscreenUserManagerImpl implements private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); private final UserManager mUserManager; private final IStatusBarService mBarService; + private final List<UserChangedListener> mListeners = new ArrayList<>(); private boolean mShowLockscreenNotifications; private boolean mAllowLockscreenRemoteInput; @@ -112,6 +115,10 @@ public class NotificationLockscreenUserManagerImpl implements updatePublicMode(); mPresenter.onUserSwitched(mCurrentUserId); getEntryManager().getNotificationData().filterAndSort(); + + for (UserChangedListener listener : mListeners) { + listener.onUserChanged(mCurrentUserId); + } } else if (Intent.ACTION_USER_ADDED.equals(action)) { updateCurrentProfilesCache(); } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { @@ -502,6 +509,10 @@ public class NotificationLockscreenUserManagerImpl implements } } + @Override + public void addUserChangedListener(UserChangedListener listener) { + mListeners.add(listener); + } // public void updatePublicMode() { // //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns @@ -541,6 +552,7 @@ public class NotificationLockscreenUserManagerImpl implements public void destroy() { mContext.unregisterReceiver(mBaseBroadcastReceiver); mContext.unregisterReceiver(mAllUsersReceiver); + mListeners.clear(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 7412702abfea..98a3a547ed2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -102,6 +103,7 @@ public class NotificationMediaManager implements Dumpable { private final Context mContext; private final MediaSessionManager mMediaSessionManager; private final ArrayList<MediaListener> mMediaListeners; + private final MediaArtworkProcessor mMediaArtworkProcessor; protected NotificationPresenter mPresenter; private MediaController mMediaController; @@ -133,6 +135,7 @@ public class NotificationMediaManager implements Dumpable { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } + mMediaArtworkProcessor.clearCache(); mMediaMetadata = metadata; dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } @@ -143,8 +146,10 @@ public class NotificationMediaManager implements Dumpable { Context context, Lazy<ShadeController> shadeController, Lazy<StatusBarWindowController> statusBarWindowController, - NotificationEntryManager notificationEntryManager) { + NotificationEntryManager notificationEntryManager, + MediaArtworkProcessor mediaArtworkProcessor) { mContext = context; + mMediaArtworkProcessor = mediaArtworkProcessor; mMediaListeners = new ArrayList<>(); mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); @@ -366,6 +371,7 @@ public class NotificationMediaManager implements Dumpable { } private void clearCurrentMediaNotificationSession() { + mMediaArtworkProcessor.clearCache(); mMediaMetadata = null; if (mMediaController != null) { if (DEBUG_MEDIA) { @@ -418,7 +424,19 @@ public class NotificationMediaManager implements Dumpable { // might still be null } if (artworkBitmap != null) { - artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap); + int notificationColor; + synchronized (mEntryManager.getNotificationData()) { + NotificationEntry entry = mEntryManager.getNotificationData() + .get(mMediaNotificationKey); + if (entry == null || entry.getRow() == null) { + notificationColor = Color.TRANSPARENT; + } else { + notificationColor = entry.getRow().calculateBgColor(); + } + } + Bitmap bmp = mMediaArtworkProcessor.processArtwork(mContext, artworkBitmap, + notificationColor); + artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); } } boolean allowWhenShade = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java index 8c29fb50f00a..54ed0d9cd8ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java @@ -333,6 +333,7 @@ public class NotificationData { mGroupManager.onEntryUpdated(entry, oldSbn); } entry.populateFromRanking(mTmpRanking); + entry.setIsHighPriority(isHighPriority(entry.notification)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index ee551ee96e7b..2e93c3822737 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -153,6 +153,12 @@ public final class NotificationEntry { */ private boolean mUserDismissedBubble; + /** + * Whether this notification is shown to the user as a high priority notification: visible on + * the lock screen/status bar and in the top section in the shade. + */ + private boolean mHighPriority; + public NotificationEntry(StatusBarNotification n) { this(n, null); } @@ -191,6 +197,14 @@ public final class NotificationEntry { return interruption; } + public boolean isHighPriority() { + return mHighPriority; + } + + public void setIsHighPriority(boolean highPriority) { + this.mHighPriority = highPriority; + } + public void setIsBubble(boolean bubbleable) { mIsBubble = bubbleable; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 7b94c7450637..35b7ef42177d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -167,13 +167,10 @@ public class NotificationLogger implements StateListener { */ public static NotificationVisibility.NotificationLocation getNotificationLocation( NotificationEntry entry) { - ExpandableNotificationRow row = entry.getRow(); - ExpandableViewState childViewState = row.getViewState(); - - if (childViewState == null) { + if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) { return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN; } - return convertNotificationLocation(childViewState.location); + return convertNotificationLocation(entry.getRow().getViewState().location); } private static NotificationVisibility.NotificationLocation convertNotificationLocation( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 296c061459bc..bed2426021a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -3092,6 +3092,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */ + public void setDismissRtl(boolean dismissRtl) { + mMenuRow.setDismissRtl(dismissRtl); + } + private static class NotificationViewState extends ExpandableViewState { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 5e9f20795272..cb1384cacec8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -300,7 +300,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx row.getIsNonblockable(), isForBlockingHelper, row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE, - row.getEntry().importance); + row.getEntry().importance, + row.getEntry().isHighPriority()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 5253e38fd675..2a9a815e12d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -21,7 +21,6 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -97,8 +96,12 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private int mNumUniqueChannelsInRow; private NotificationChannel mSingleNotificationChannel; private int mStartingChannelImportance; - private int mStartingChannelOrNotificationImportance; - private int mChosenImportance; + private boolean mWasShownHighPriority; + /** + * The last importance level chosen by the user. Null if the user has not chosen an importance + * level; non-null once the user takes an action which indicates an explicit preference. + */ + @Nullable private Integer mChosenImportance; private boolean mIsSingleDefaultChannel; private boolean mIsNonblockable; private StatusBarNotification mSbn; @@ -195,13 +198,14 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G final OnAppSettingsClickListener onAppSettingsClick, boolean isDeviceProvisioned, boolean isNonblockable, - int importance) + int importance, + boolean wasShownHighPriority) throws RemoteException { bindNotification(pm, iNotificationManager, pkg, notificationChannel, numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick, onAppSettingsClick, isDeviceProvisioned, isNonblockable, false /* isBlockingHelper */, false /* isUserSentimentNegative */, - importance); + importance, wasShownHighPriority); } public void bindNotification( @@ -218,7 +222,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isNonblockable, boolean isForBlockingHelper, boolean isUserSentimentNegative, - int importance) + int importance, + boolean wasShownHighPriority) throws RemoteException { mINotificationManager = iNotificationManager; mMetricsLogger = Dependency.get(MetricsLogger.class); @@ -231,10 +236,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mCheckSaveListener = checkSaveListener; mOnSettingsClickListener = onSettingsClick; mSingleNotificationChannel = notificationChannel; - int channelImportance = mSingleNotificationChannel.getImportance(); - mStartingChannelImportance = mChosenImportance = channelImportance; - mStartingChannelOrNotificationImportance = - channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance; + mStartingChannelImportance = mSingleNotificationChannel.getImportance(); + mWasShownHighPriority = wasShownHighPriority; mNegativeUserSentiment = isUserSentimentNegative; mIsNonblockable = isNonblockable; mIsForeground = @@ -400,19 +403,27 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G * @return new LogMaker */ private LogMaker importanceChangeLogMaker() { + Integer chosenImportance = + mChosenImportance != null ? mChosenImportance : mStartingChannelImportance; return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE) .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(mChosenImportance - mStartingChannelImportance); + .setSubtype(chosenImportance - mStartingChannelImportance); } private boolean hasImportanceChanged() { return mSingleNotificationChannel != null - && mStartingChannelImportance != mChosenImportance; + && mChosenImportance != null + && (mStartingChannelImportance != mChosenImportance + || (mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT) + || (!mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT)); } private void saveImportance() { if (!mIsNonblockable || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) { + if (mChosenImportance == null) { + mChosenImportance = mStartingChannelImportance; + } updateImportance(); } } @@ -421,12 +432,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G * Commits the updated importance values on the background thread. */ private void updateImportance() { - mMetricsLogger.write(importanceChangeLogMaker()); - - Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, - mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null, - mStartingChannelImportance, mChosenImportance)); + if (mChosenImportance != null) { + mMetricsLogger.write(importanceChangeLogMaker()); + + Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); + bgHandler.post( + new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, + mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null, + mStartingChannelImportance, mChosenImportance)); + } } private void bindButtons() { @@ -444,11 +458,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G TextView silent = findViewById(R.id.int_silent); TextView alert = findViewById(R.id.int_alert); - boolean isCurrentlyAlerting = - mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT; - block.setOnClickListener(mOnStopOrMinimizeNotifications); - if (isCurrentlyAlerting) { + if (mWasShownHighPriority) { silent.setOnClickListener(mOnToggleSilent); silent.setText(R.string.inline_silent_button_silent); alert.setOnClickListener(mOnKeepShowing); @@ -517,7 +528,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G break; case ACTION_TOGGLE_SILENT: mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT; - if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) { + if (mWasShownHighPriority) { mChosenImportance = IMPORTANCE_LOW; confirmationText.setText(R.string.notification_channel_silenced); } else { @@ -584,9 +595,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G @Override public void onFinishedClosing() { - mStartingChannelImportance = mChosenImportance; - if (mChosenImportance != IMPORTANCE_UNSPECIFIED) { - mStartingChannelOrNotificationImportance = mChosenImportance; + if (mChosenImportance != null) { + mStartingChannelImportance = mChosenImportance; } mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index d97162c2ef1e..d83a158b319f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -23,7 +23,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Notification; -import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -78,6 +77,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private ArrayList<MenuItem> mRightMenuItems; private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>(); private OnMenuEventListener mMenuListener; + private boolean mDismissRtl; + private boolean mIsForeground; private ValueAnimator mFadeAnimator; private boolean mAnimating; @@ -238,6 +239,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } private void createMenuViews(boolean resetState, final boolean isForeground) { + mIsForeground = isForeground; + final Resources res = mContext.getResources(); mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); @@ -250,12 +253,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } mAppOpsItem = createAppOpsItem(mContext); if (NotificationUtils.useNewInterruptionModel(mContext)) { - int channelImportance = mParent.getEntry().channel.getImportance(); - int effectiveImportance = - channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED - ? mParent.getEntry().importance : channelImportance; - mInfoItem = createInfoItem(mContext, - effectiveImportance < NotificationManager.IMPORTANCE_DEFAULT); + mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority()); } else { mInfoItem = createInfoItem(mContext); } @@ -268,10 +266,11 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mRightMenuItems.add(mAppOpsItem); mLeftMenuItems.addAll(mRightMenuItems); } else { - mRightMenuItems.add(mInfoItem); - mRightMenuItems.add(mAppOpsItem); + ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems; + menuItems.add(mInfoItem); + menuItems.add(mAppOpsItem); if (!isForeground) { - mRightMenuItems.add(mSnoozeItem); + menuItems.add(mSnoozeItem); } } @@ -729,6 +728,14 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl return getParent().canViewBeDismissed(); } + @Override + public void setDismissRtl(boolean dismissRtl) { + mDismissRtl = dismissRtl; + if (mMenuContainer != null) { + createMenuViews(true, mIsForeground); + } + } + public static class NotificationMenuItem implements MenuItem { View mMenuView; GutsContent mGutsContent; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java index db7b4fc189aa..4bdc1705c5ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java @@ -17,8 +17,15 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.os.Build; import android.view.View; +import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -42,6 +49,47 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { } @Override + public void onReinflated() { + super.onReinflated(); + + Configuration configuration = mView.getResources().getConfiguration(); + boolean nightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES; + + float[] hsl = new float[] {0f, 0f, 0f}; + ColorUtils.colorToHSL(mBackgroundColor, hsl); + boolean backgroundIsDark = Color.alpha(mBackgroundColor) == 0 + || hsl[1] == 0 && hsl[2] < 0.5; + boolean backgroundHasColor = hsl[1] > 0; + + // Let's invert the notification colors when we're in night mode and + // the notification background isn't colorized. + if (!backgroundIsDark && !backgroundHasColor && nightMode + && mRow.getEntry().targetSdk < Build.VERSION_CODES.Q) { + Paint paint = new Paint(); + ColorMatrix matrix = new ColorMatrix(); + ColorMatrix tmp = new ColorMatrix(); + // Inversion should happen on Y'UV space to conseve the colors and + // only affect the luminosity. + matrix.setRGB2YUV(); + tmp.set(new float[]{ + -1f, 0f, 0f, 0f, 255f, + 0f, 1f, 0f, 0f, 0f, + 0f, 0f, 1f, 0f, 0f, + 0f, 0f, 0f, 1f, 0f + }); + matrix.postConcat(tmp); + tmp.setYUV2RGB(); + matrix.postConcat(tmp); + paint.setColorFilter(new ColorMatrixColorFilter(matrix)); + mView.setLayerType(View.LAYER_TYPE_HARDWARE, paint); + + hsl[2] = 1f - hsl[2]; + mBackgroundColor = ColorUtils.HSLToColor(hsl); + } + } + + @Override protected boolean shouldClearBackgroundOnReapply() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 1efdc56874f3..9258c9971451 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -37,7 +37,7 @@ public abstract class NotificationViewWrapper implements TransformableView { protected final View mView; protected final ExpandableNotificationRow mRow; - private int mBackgroundColor = 0; + protected int mBackgroundColor = 0; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 2129b81b1448..63b34d185c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -174,6 +174,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private final boolean mShouldDrawNotificationBackground; private boolean mLowPriorityBeforeSpeedBump; private final boolean mAllowLongPress; + private boolean mDismissRtl; private float mExpandedHeight; private int mOwnScrollY; @@ -533,8 +534,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd tunerService.addTunable((key, newValue) -> { if (key.equals(LOW_PRIORITY)) { mLowPriorityBeforeSpeedBump = "1".equals(newValue); + } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) { + updateDismissRtlSetting("1".equals(newValue)); } - }, LOW_PRIORITY); + }, LOW_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL); mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override @@ -548,6 +551,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd }); } + private void updateDismissRtlSetting(boolean dismissRtl) { + mDismissRtl = dismissRtl; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl); + } + } + } + @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onFinishInflate() { @@ -2600,8 +2613,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd View child = getChildAt(i); if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - if (!mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification())) { + if (!row.getEntry().isHighPriority()) { break; } else { lastChildBeforeGap = row; @@ -2619,8 +2631,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd View child = getChildAt(i); if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - if (!mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification())) { + if (!row.getEntry().isHighPriority()) { return row; } } @@ -3255,6 +3266,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd generateAddAnimation(child, false /* fromMoreCard */); updateAnimationState(child); updateChronometerForChild(child); + if (child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl); + } if (ANCHOR_SCROLLING) { // TODO: once we're recycling this will need to check the adapter position of the child if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) { @@ -5756,11 +5770,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd currentIndex++; boolean beforeSpeedBump; if (mLowPriorityBeforeSpeedBump) { - beforeSpeedBump = !mEntryManager.getNotificationData().isAmbient( - row.getStatusBarNotification().getKey()); + beforeSpeedBump = !row.getEntry().ambient; } else { - beforeSpeedBump = mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification()); + beforeSpeedBump = row.getEntry().isHighPriority(); } if (beforeSpeedBump) { speedBumpIndex = currentIndex; @@ -5784,8 +5796,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd continue; } ExpandableNotificationRow row = (ExpandableNotificationRow) view; - if (!mEntryManager.getNotificationData().isHighPriority( - row.getStatusBarNotification())) { + if (!row.getEntry().isHighPriority()) { if (currentIndex > 0) { gapIndex = currentIndex; } @@ -6315,7 +6326,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) { boolean isValidDirection; if (NotificationUtils.useNewInterruptionModel(mContext)) { - isValidDirection = isLayoutRtl() ? !isRightOrDown : isRightOrDown; + isValidDirection = mDismissRtl ? !isRightOrDown : isRightOrDown; } else { isValidDirection = true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index fac4dbbe735a..b96c55b37c48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -168,8 +168,8 @@ public class AutoTileManager { @Override public void onAutoModeChanged(int autoMode) { - if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM - || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) { + if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME + || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) { addNightTile(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 03375d20e6db..e953ad53527b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -113,6 +113,7 @@ public class KeyguardStatusBarView extends RelativeLayout * Ratio representing being in ambient mode or not. */ private float mDarkAmount; + private boolean mDozing; public KeyguardStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -210,7 +211,7 @@ public class KeyguardStatusBarView extends RelativeLayout mMultiUserSwitch.setVisibility(View.GONE); } } - mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable); + mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable || mDozing); } private void updateSystemIconsLayoutParams() { @@ -347,7 +348,7 @@ public class KeyguardStatusBarView extends RelativeLayout mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); onThemeChanged(); - updateDozeState(); + updateDarkState(); } @Override @@ -506,21 +507,29 @@ public class KeyguardStatusBarView extends RelativeLayout } } + public void setDozing(boolean dozing) { + if (mDozing == dozing) { + return; + } + mDozing = dozing; + updateVisibilities(); + } + public void setDarkAmount(float darkAmount) { mDarkAmount = darkAmount; if (darkAmount == 0) { dozeTimeTick(); } - updateDozeState(); + updateDarkState(); } public void dozeTimeTick() { mCurrentBurnInOffsetX = getBurnInOffset(mBurnInOffset, true /* xAxis */); mCurrentBurnInOffsetY = getBurnInOffset(mBurnInOffset, false /* xAxis */); - updateDozeState(); + updateDarkState(); } - private void updateDozeState() { + private void updateDarkState() { float alpha = 1f - mDarkAmount; int visibility = alpha != 0f ? VISIBLE : INVISIBLE; mCarrierLabel.setAlpha(alpha * alpha); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java index a5d938216f5c..39fbbb17c498 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java @@ -20,6 +20,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; +import static com.android.systemui.statusbar.phone.NavigationPrototypeController.PROTOTYPE_ENABLED; import android.annotation.NonNull; import android.content.Context; @@ -84,7 +85,7 @@ public abstract class NavigationGestureAction { // Tell launcher that this action requires a stable task list or not boolean flag = requiresStableTaskList(); - if (flag != sLastTaskStabilizationFlag) { + if (getGlobalBoolean(PROTOTYPE_ENABLED) && flag != sLastTaskStabilizationFlag) { Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(), ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0); sLastTaskStabilizationFlag = flag; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index a09e5858d576..f762a6a68ad6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -37,6 +37,7 @@ public class NavigationPrototypeController extends ContentObserver { private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map"; public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable"; + public static final String PROTOTYPE_ENABLED = "prototype_enabled"; @Retention(RetentionPolicy.SOURCE) @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK, 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 077fcda70f0c..e86996a81bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -194,8 +194,7 @@ public class NotificationIconAreaController implements DarkReceiver, if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) { return false; } - if (!showLowPriority - && !mEntryManager.getNotificationData().isHighPriority(entry.notification)) { + if (!showLowPriority && !entry.isHighPriority()) { return false; } if (!entry.isTopLevelChild()) { 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 0d5ebb9b6578..c10837165c0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -140,7 +140,8 @@ public class NotificationPanelView extends PanelView implements private KeyguardAffordanceHelper mAffordanceHelper; private KeyguardUserSwitcher mKeyguardUserSwitcher; - private KeyguardStatusBarView mKeyguardStatusBar; + @VisibleForTesting + protected KeyguardStatusBarView mKeyguardStatusBar; private ViewGroup mBigClockContainer; private QS mQs; private FrameLayout mQsFrame; @@ -2792,6 +2793,7 @@ public class NotificationPanelView extends PanelView implements if (mDozing) { mNotificationStackScroller.setShowDarkShelf(!hasCustomClock()); } + mKeyguardStatusBar.setDozing(mDozing); if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 43c35f11d9e5..18711c0d1ae3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -173,7 +173,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mProvisionedController = Dependency.get(DeviceProvisionedController.class); mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); mLocationController = Dependency.get(LocationController.class); - mPrivacyItemController = new PrivacyItemController(mContext, this); + mPrivacyItemController = Dependency.get(PrivacyItemController.class); mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast); mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot); @@ -266,7 +266,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mNextAlarmController.addCallback(mNextAlarmCallback); mDataSaver.addCallback(this); mKeyguardMonitor.addCallback(this); - mPrivacyItemController.setListening(true); + mPrivacyItemController.addCallback(this); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener); @@ -294,7 +294,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mNextAlarmController.removeCallback(mNextAlarmCallback); mDataSaver.removeCallback(this); mKeyguardMonitor.removeCallback(this); - mPrivacyItemController.setListening(false); + mPrivacyItemController.removeCallback(this); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallback(this); mContext.unregisterReceiver(mIntentReceiver); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 60a20cf15cc0..e80275793b28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -61,7 +61,6 @@ public class BubbleControllerTest extends SysuiTestCase { private IActivityManager mActivityManager; @Mock private DozeParameters mDozeParameters; - @Mock private FrameLayout mStatusBarView; @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; @@ -80,6 +79,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mStatusBarView = new FrameLayout(mContext); mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager); // Bubbles get added to status bar window view diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java new file mode 100644 index 000000000000..1bb7ef4a657b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.content.res.Resources; +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private ExpandedAnimationController mExpandedController = new ExpandedAnimationController(); + + private int mStackOffset; + private float mBubblePadding; + private float mBubbleSize; + + private PointF mExpansionPoint; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mExpandedController); + Resources res = mLayout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + } + + @Test + public void testExpansionAndCollapse() throws InterruptedException { + mExpansionPoint = new PointF(100, 100); + Runnable afterExpand = Mockito.mock(Runnable.class); + mExpandedController.expandFromStack(mExpansionPoint, afterExpand); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testExpanded(); + Mockito.verify(afterExpand).run(); + + Runnable afterCollapse = Mockito.mock(Runnable.class); + mExpandedController.collapseBackToStack(afterCollapse); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); + Mockito.verify(afterExpand).run(); + } + + /** Check that children are in the correct positions for being stacked. */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** Check that children are in the correct positions for being expanded. */ + private void testExpanded() { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)), + mViews.get(i).getTranslationX(), + 2f); + assertEquals(mBubblePadding + mCutoutInsetSize, + mViews.get(i).getTranslationY(), 2f); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java new file mode 100644 index 000000000000..bfc02d902416 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; + +import android.os.SystemClock; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.google.android.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.mockito.Spy; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +/** Tests the PhysicsAnimationLayout itself, with a basic test animation controller. */ +public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { + static final float TEST_TRANSLATION_X_OFFSET = 15f; + + @Spy + private TestableAnimationController mTestableController = new TestableAnimationController(); + + @Before + public void setUp() throws Exception { + super.setUp(); + + // By default, use translation animations, chain the X animations with the default + // offset, and don't actually remove views immediately (since most implementations will wait + // to animate child views out before actually removing them). + mTestableController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + mTestableController.setChainedProperties(Sets.newHashSet(DynamicAnimation.TRANSLATION_X)); + mTestableController.setOffsetForProperty( + DynamicAnimation.TRANSLATION_X, TEST_TRANSLATION_X_OFFSET); + mTestableController.setRemoveImmediately(false); + } + + @Test + public void testRenderVisibility() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // The last child should be GONE, the rest VISIBLE. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE, + mLayout.getChildAt(i).getVisibility()); + } + } + + @Test + public void testHierarchyChanges() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Make sure the controller was notified of all the views we added. + for (View mView : mViews) { + Mockito.verify(mTestableController).onChildAdded(mView, 0); + } + + // Remove some views and ensure the controller was notified, with the proper indices. + mTestableController.setRemoveImmediately(true); + mLayout.removeView(mViews.get(1)); + mLayout.removeView(mViews.get(2)); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(1)), eq(1), any()); + Mockito.verify(mTestableController).onChildToBeRemoved( + eq(mViews.get(2)), eq(1), any()); + + // Make sure we still get view added notifications after doing some removals. + final View newBubble = new FrameLayout(mContext); + mLayout.addView(newBubble, 0); + Mockito.verify(mTestableController).onChildAdded(newBubble, 0); + } + + @Test + public void testUpdateValueNotChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Don't chain any values. + mTestableController.setChainedProperties(Sets.newHashSet()); + + // Child views should not be translated. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + // Animate the first child's translation X. + final CountDownLatch animLatch = new CountDownLatch(1); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100, + animLatch::countDown); + animLatch.await(1, TimeUnit.SECONDS); + + // Ensure that the first view has been translated, but not the second one. + assertEquals(100, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + } + + @Test + public void testUpdateValueXChained() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + testChainedTranslationAnimations(); + } + + @Test + public void testSetEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + final CountDownLatch yLatch = new CountDownLatch(1); + final OneTimeEndListener yEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + yLatch.countDown(); + } + }); + + // Set end listeners for both x and y. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + mLayout.setEndListenerForProperty(yEndListener, DynamicAnimation.TRANSLATION_Y); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + yLatch.await(1, TimeUnit.SECONDS); + + // Make sure the x end listener was called only one time, and the y listener was never + // called since we didn't animate y. Wait 1 second after the original animation end trigger + // to make sure it doesn't get called again. + Mockito.verify(xEndListener, Mockito.after(1000).times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + Mockito.verify(yEndListener, Mockito.after(1000).never()) + .onAnimationEnd(any(), anyBoolean(), anyFloat(), anyFloat()); + } + + @Test + public void testRemoveEndListeners() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + mTestableController.setChainedProperties(Sets.newHashSet()); + + final CountDownLatch xLatch = new CountDownLatch(1); + OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + xLatch.countDown(); + } + }); + + // Set the end listener. + mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X); + + // Animate x, and wait for it to finish. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + xLatch.await(); + + InOrder endListenerCalls = inOrder(xEndListener); + endListenerCalls.verify(xEndListener, Mockito.times(1)) + .onAnimationEnd( + any(), + eq(false), + eq(100f), + anyFloat()); + + // Animate X again, remove the end listener. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X); + xLatch.await(1, TimeUnit.SECONDS); + + // Make sure the end listener was not called. + endListenerCalls.verifyNoMoreInteractions(); + } + + @Test + public void testPrecedingNonRemovedIndex() { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + // Call removeView at index 4, but don't actually remove it yet (as if we're animating it + // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since + // 4 is on its way out. + assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3)); + mLayout.removeView(mViews.get(4)); + assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3)); + + // Call removeView at index 1, and actually remove it immediately. With the old view at 1 + // instantly gone, the preceding view to 0 should be 1 in both cases. + assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); + mTestableController.setRemoveImmediately(true); + mLayout.removeView(mViews.get(1)); + assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0)); + } + + @Test + public void testSetController() throws InterruptedException { + // Add the bubbles, then set the controller, to make sure that a controller added to an + // already-initialized view works correctly. + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mTestableController); + testChainedTranslationAnimations(); + + TestableAnimationController secondController = + Mockito.spy(new TestableAnimationController()); + secondController.setAnimatedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y)); + secondController.setChainedProperties(Sets.newHashSet( + DynamicAnimation.SCALE_X)); + secondController.setOffsetForProperty( + DynamicAnimation.SCALE_X, 10f); + secondController.setRemoveImmediately(true); + + mLayout.setController(secondController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.SCALE_X, + 0, + 1.5f); + + waitForPropertyAnimations(DynamicAnimation.SCALE_X); + + // Make sure we never asked the original controller about any SCALE animations, that would + // mean the controller wasn't switched over properly. + Mockito.verify(mTestableController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(mTestableController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + // Make sure we asked the new controller about its animated properties, and configuration + // options. + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getAnimatedProperties(); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt()); + Mockito.verify(secondController, Mockito.atLeastOnce()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); + + mLayout.setController(mTestableController); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Make sure we never asked the second controller about the TRANSLATION_X animation. + Mockito.verify(secondController, Mockito.never()) + .getNextAnimationInChain(eq(DynamicAnimation.TRANSLATION_X), anyInt()); + Mockito.verify(secondController, Mockito.never()) + .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.TRANSLATION_X)); + + } + + @Test + public void testArePropertiesAnimating() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + // Wait for the animations to get underway. + SystemClock.sleep(50); + + assertTrue(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_X)); + assertFalse(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_Y)); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + assertFalse(mLayout.arePropertiesAnimating( + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); + } + + @Test + public void testCancelAllAnimations() throws InterruptedException { + mLayout.setController(mTestableController); + addOneMoreThanRenderLimitBubbles(); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 1000); + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 1000); + + mLayout.cancelAllAnimations(); + + waitForLayoutMessageQueue(); + + // Animations should be somewhere before their end point. + assertTrue(mViews.get(0).getTranslationX() < 1000); + assertTrue(mViews.get(0).getTranslationY() < 1000); + } + + + /** Standard test of chained translation animations. */ + private void testChainedTranslationAnimations() throws InterruptedException { + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); + + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_X, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); + + // Since we enabled chaining, animating the first view to 100 should animate the second to + // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble + // not being visible, or animated, make sure that it has the appropriate chained + // translation. + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + assertEquals( + 100 + i * TEST_TRANSLATION_X_OFFSET, + mLayout.getChildAt(i).getTranslationX(), .1f); + } + + // Ensure that the Y translations were unaffected. + assertEquals(0, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + + // Animate the first child's Y translation. + mLayout.animateValueForChildAtIndex( + DynamicAnimation.TRANSLATION_Y, + 0, + 100); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_Y); + + // Ensure that only the first view's Y translation chained, since we only chained X + // translations. + assertEquals(100, mLayout.getChildAt(0).getTranslationY(), .1f); + assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f); + } + + /** + * Animation controller with configuration methods whose return values can be set by individual + * tests. + */ + private class TestableAnimationController + extends PhysicsAnimationLayout.PhysicsAnimationController { + private Set<DynamicAnimation.ViewProperty> mAnimatedProperties = new HashSet<>(); + private Set<DynamicAnimation.ViewProperty> mChainedProperties = new HashSet<>(); + private HashMap<DynamicAnimation.ViewProperty, Float> mOffsetForProperty = new HashMap<>(); + private boolean mRemoveImmediately = false; + + void setAnimatedProperties( + Set<DynamicAnimation.ViewProperty> animatedProperties) { + mAnimatedProperties = animatedProperties; + } + + void setChainedProperties( + Set<DynamicAnimation.ViewProperty> chainedProperties) { + mChainedProperties = chainedProperties; + } + + void setOffsetForProperty( + DynamicAnimation.ViewProperty property, float offset) { + mOffsetForProperty.put(property, offset); + } + + public void setRemoveImmediately(boolean removeImmediately) { + mRemoveImmediately = removeImmediately; + } + + @Override + Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { + return mAnimatedProperties; + } + + @Override + int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) { + return mChainedProperties.contains(property) ? index + 1 : NONE; + } + + @Override + float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) { + return mOffsetForProperty.getOrDefault(property, 0f); + } + + @Override + SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) { + return new SpringForce(); + } + + @Override + void onChildAdded(View child, int index) {} + + @Override + void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) { + if (mRemoveImmediately) { + actuallyRemove.run(); + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java new file mode 100644 index 000000000000..186a76219536 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.DisplayCutout; +import android.view.View; +import android.view.WindowInsets; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a + * testable version of the layout, and provides some helpful methods to add views to the layout and + * wait for physics animations to finish running. + * + * See physics-animation-testing.md. + */ +public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { + TestablePhysicsAnimationLayout mLayout; + List<View> mViews = new ArrayList<>(); + + Handler mMainThreadHandler; + + int mMaxRenderedBubbles; + int mSystemWindowInsetSize = 50; + int mCutoutInsetSize = 100; + + int mWidth = 1000; + int mHeight = 1000; + + @Mock + private WindowInsets mWindowInsets; + + @Mock + private DisplayCutout mCutout; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mLayout = new TestablePhysicsAnimationLayout(mContext); + mLayout.setLeft(0); + mLayout.setRight(mWidth); + mLayout.setTop(0); + mLayout.setBottom(mHeight); + + mMaxRenderedBubbles = + getContext().getResources().getInteger(R.integer.bubbles_max_rendered); + mMainThreadHandler = new Handler(Looper.getMainLooper()); + + when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize); + when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize); + + when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout); + when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize); + when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize); + } + + /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ + void addOneMoreThanRenderLimitBubbles() { + for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + final View newView = new FrameLayout(mContext); + mLayout.addView(newView, 0); + mViews.add(0, newView); + + newView.setTranslationX(0); + newView.setTranslationY(0); + } + } + + /** + * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties' + * animations to finish before allowing the test to proceed. + */ + void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties) + throws InterruptedException { + final CountDownLatch animLatch = new CountDownLatch(properties.length); + for (DynamicAnimation.ViewProperty property : properties) { + mLayout.setTestEndListenerForProperty(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + animLatch.countDown(); + } + }, property); + } + animLatch.await(1, TimeUnit.SECONDS); + } + + /** Uses a latch to wait for the message queue to finish. */ + void waitForLayoutMessageQueue() throws InterruptedException { + // Wait for layout, then the view should be actually removed. + CountDownLatch layoutLatch = new CountDownLatch(1); + mLayout.post(layoutLatch::countDown); + layoutLatch.await(1, TimeUnit.SECONDS); + } + + /** + * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations + * are run on the main thread, which is a requirement of DynamicAnimation. + */ + protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout { + public TestablePhysicsAnimationLayout(Context context) { + super(context); + } + + @Override + public void setController(PhysicsAnimationController controller) { + mMainThreadHandler.post(() -> super.setController(controller)); + try { + waitForLayoutMessageQueue(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void cancelAllAnimations() { + mMainThreadHandler.post(super::cancelAllAnimations); + } + + @Override + protected void animateValueForChildAtIndex(DynamicAnimation.ViewProperty property, + int index, float value, float startVel, Runnable after) { + mMainThreadHandler.post(() -> + super.animateValueForChildAtIndex(property, index, value, startVel, after)); + } + + @Override + public WindowInsets getRootWindowInsets() { + return mWindowInsets; + } + + /** + * Sets an end listener that will be called after the 'real' end listener that was already + * set. + */ + private void setTestEndListenerForProperty(DynamicAnimation.OnAnimationEndListener listener, + DynamicAnimation.ViewProperty property) { + final DynamicAnimation.OnAnimationEndListener realEndListener = + mEndListenerForProperty.get(property); + + setEndListenerForProperty((animation, canceled, value, velocity) -> { + if (realEndListener != null) { + realEndListener.onAnimationEnd(animation, canceled, value, velocity); + } + + listener.onAnimationEnd(animation, canceled, value, velocity); + }, property); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java new file mode 100644 index 000000000000..0f686df87ca5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles.animation; + +import static org.junit.Assert.assertEquals; + +import android.graphics.PointF; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.systemui.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase { + + @Spy + private TestableStackController mStackController = new TestableStackController(); + + private int mStackOffset; + + @Before + public void setUp() throws Exception { + super.setUp(); + addOneMoreThanRenderLimitBubbles(); + mLayout.setController(mStackController); + mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); + } + + /** + * Test moving around the stack, and make sure the position is updated correctly, and the stack + * direction is correct. + */ + @Test + public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException { + mStackController.moveFirstBubbleWithStackFollowing(200, 100); + + // The first bubble should have moved instantly, the rest should be waiting for animation. + assertEquals(200, mViews.get(0).getTranslationX(), .1f); + assertEquals(100, mViews.get(0).getTranslationY(), .1f); + assertEquals(0, mViews.get(1).getTranslationX(), .1f); + assertEquals(0, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack got moved to the right place and is stacked to the left. + testStackedAtPosition(200, 100, -1); + assertEquals(new PointF(200, 100), mStackController.getStackPosition()); + + mStackController.moveFirstBubbleWithStackFollowing(1000, 500); + + // The first bubble again should have moved instantly while the rest remained where they + // were until the animation takes over. + assertEquals(1000, mViews.get(0).getTranslationX(), .1f); + assertEquals(500, mViews.get(0).getTranslationY(), .1f); + assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f); + assertEquals(100, mViews.get(1).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + testStackedAtPosition(1000, 500, 1); + assertEquals(new PointF(1000, 500), mStackController.getStackPosition()); + } + + @Test + public void testFlingSideways() throws InterruptedException { + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 5000f, 1.15f, new SpringForce(), mWidth * 1f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 0f, 1.15f, new SpringForce(), 0f); + + // Nothing should move initially since the animations haven't begun, including the first + // view. + assertEquals(0f, mViews.get(0).getTranslationX(), 1f); + assertEquals(0f, mViews.get(0).getTranslationY(), 1f); + + // Wait for the flinging. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Wait for the springing. + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung all the way to the right side, with the + // stack stacked off to the right now. + testStackedAtPosition(mWidth * 1f, 0f, 1); + } + + @Test + public void testFlingUpFromBelowBottomCenter() throws InterruptedException { + // Move to the center of the screen, just past the bottom. + mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + + // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much + // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top + // but should bounce back down. + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_X, + 0, 1.15f, new SpringForce(), 27f); + mStackController.flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.TRANSLATION_Y, + 5000f, 1.15f, new SpringForce(), 27f); + + // Nothing should move initially since the animations haven't begun. + assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f); + assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f); + + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + // Once the dust has settled, we should have flung a bit but then sprung to the final + // destination which is (27, 27). + testStackedAtPosition(27, 27, -1); + } + + @Test + public void testChildAdded() throws InterruptedException { + // Move the stack to y = 500. + mStackController.moveFirstBubbleWithStackFollowing(0f, 500f); + waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y); + + final View newView = new FrameLayout(mContext); + mLayout.addView( + newView, + 0, + new FrameLayout.LayoutParams(50, 50)); + + waitForPropertyAnimations( + DynamicAnimation.TRANSLATION_X, + DynamicAnimation.TRANSLATION_Y, + DynamicAnimation.SCALE_X, + DynamicAnimation.SCALE_Y); + + // The new view should be at the top of the stack, in the correct position. + assertEquals(0f, newView.getTranslationX(), .1f); + assertEquals(500f, newView.getTranslationY(), .1f); + assertEquals(1f, newView.getScaleX(), .1f); + assertEquals(1f, newView.getScaleY(), .1f); + assertEquals(1f, newView.getAlpha(), .1f); + } + + @Test + public void testChildRemoved() throws InterruptedException { + final View firstView = mLayout.getChildAt(0); + mLayout.removeView(firstView); + + // The view should still be there, since the controller is animating it out and hasn't yet + // actually removed it from the parent view. + assertEquals(0, mLayout.indexOfChild(firstView)); + + waitForPropertyAnimations(DynamicAnimation.ALPHA); + waitForLayoutMessageQueue(); + + assertEquals(-1, mLayout.indexOfChild(firstView)); + + // The subsequent view should have been translated over to 0, not stacked off to the left. + assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); + } + + /** + * Checks every child view to make sure it's stacked at the given coordinates, off to the left + * or right side depending on offset multiplier. + */ + private void testStackedAtPosition(float x, float y, int offsetMultiplier) { + // Make sure the rest of the stack moved again, including the first bubble not moving, and + // is stacked to the right now that we're on the right side of the screen. + for (int i = 0; i < mLayout.getChildCount(); i++) { + assertEquals(x + i * offsetMultiplier * mStackOffset, + mViews.get(i).getTranslationX(), 2f); + assertEquals(y, mViews.get(i).getTranslationY(), 2f); + } + } + + /** + * Testable version of the stack controller that dispatches its animations on the main thread. + */ + private class TestableStackController extends StackAnimationController { + @Override + public void flingThenSpringFirstBubbleWithStackFollowing( + DynamicAnimation.ViewProperty property, float vel, float friction, + SpringForce spring, Float finalPosition) { + mMainThreadHandler.post(() -> + super.flingThenSpringFirstBubbleWithStackFollowing( + property, vel, friction, spring, finalPosition)); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index e6d7ee7407d4..98bf3c2743a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -45,9 +45,12 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -73,6 +76,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { private lateinit var userManager: UserManager @Captor private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>> + @Captor + private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback> private lateinit var testableLooper: TestableLooper private lateinit var privacyItemController: PrivacyItemController @@ -95,12 +100,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { } })).`when`(userManager).getProfiles(anyInt()) - privacyItemController = PrivacyItemController(mContext, callback) + privacyItemController = PrivacyItemController(mContext) } @Test - fun testSetListeningTrue() { - privacyItemController.setListening(true) + fun testSetListeningTrueByAddingCallback() { + privacyItemController.addCallback(callback) verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any(AppOpsController.Callback::class.java)) testableLooper.processAllMessages() @@ -108,6 +113,13 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test + fun testSetListeningTrue() { + privacyItemController.setListening(true) + verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), + any(AppOpsController.Callback::class.java)) + } + + @Test fun testSetListeningFalse() { privacyItemController.setListening(true) privacyItemController.setListening(false) @@ -121,7 +133,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) - privacyItemController.setListening(true) + privacyItemController.addCallback(callback) testableLooper.processAllMessages() verify(callback).privacyChanged(capture(argCaptor)) assertEquals(1, argCaptor.value.size) @@ -131,7 +143,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testSystemApps() { doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, SYSTEM_UID, TEST_PACKAGE_NAME, 0))).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) - privacyItemController.setListening(true) + privacyItemController.addCallback(callback) testableLooper.processAllMessages() verify(callback).privacyChanged(capture(argCaptor)) assertEquals(1, argCaptor.value.size) @@ -142,8 +154,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testRegisterReceiver_allUsers() { val spiedContext = spy(mContext) - val itemController = PrivacyItemController(spiedContext, callback) - + val itemController = PrivacyItemController(spiedContext) + itemController.setListening(true) verify(spiedContext, atLeastOnce()).registerReceiverAsUser( eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null), eq(null)) @@ -170,4 +182,54 @@ class PrivacyItemControllerTest : SysuiTestCase() { Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)) verify(userManager).getProfiles(anyInt()) } + + @Test + fun testAddMultipleCallbacks() { + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + testableLooper.processAllMessages() + verify(callback).privacyChanged(anyList()) + + privacyItemController.addCallback(otherCallback) + testableLooper.processAllMessages() + verify(otherCallback).privacyChanged(anyList()) + // Adding a callback should not unnecessarily call previous ones + verifyNoMoreInteractions(callback) + } + + @Test + fun testMultipleCallbacksAreUpdated() { + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + privacyItemController.addCallback(otherCallback) + testableLooper.processAllMessages() + reset(callback) + reset(otherCallback) + + verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) + testableLooper.processAllMessages() + verify(callback).privacyChanged(anyList()) + verify(otherCallback).privacyChanged(anyList()) + } + + @Test + fun testRemoveCallback() { + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + privacyItemController.addCallback(otherCallback) + testableLooper.processAllMessages() + reset(callback) + reset(otherCallback) + + verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback)) + privacyItemController.removeCallback(callback) + argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) + testableLooper.processAllMessages() + verify(callback, never()).privacyChanged(anyList()) + verify(otherCallback).privacyChanged(anyList()) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 40d2da9a7729..fdc9e0c4d7e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -354,7 +354,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test @@ -382,16 +383,18 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test - public void testInitializeNotificationInfoView_importance() throws Exception { + public void testInitializeNotificationInfoView_highPriority() throws Exception { NotificationInfo notificationInfoView = mock(NotificationInfo.class); ExpandableNotificationRow row = spy(mHelper.createRow()); row.setBlockingHelperShowing(true); row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; row.getEntry().importance = IMPORTANCE_DEFAULT; + row.getEntry().setIsHighPriority(true); when(row.getIsNonblockable()).thenReturn(false); StatusBarNotification statusBarNotification = row.getStatusBarNotification(); @@ -411,7 +414,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(IMPORTANCE_DEFAULT)); + eq(IMPORTANCE_DEFAULT), + eq(true) /* wasShownHighPriority */); } @Test @@ -440,7 +444,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test @@ -468,7 +473,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, - eq(0)); + eq(0), + eq(false) /* wasShownHighPriority */); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index f6791dd5778c..08955e3c4d5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -210,7 +210,7 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -223,7 +223,7 @@ public class NotificationInfoTest extends SysuiTestCase { .thenReturn(iconDrawable); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -232,7 +232,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_noDelegate() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); @@ -251,7 +251,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Other")); @@ -263,7 +263,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(GONE, groupNameView.getVisibility()); final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider); @@ -280,7 +280,7 @@ public class NotificationInfoTest extends SysuiTestCase { .thenReturn(notificationChannelGroup); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -292,7 +292,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -301,7 +301,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true, - false, IMPORTANCE_DEFAULT); + false, IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, textView.getVisibility()); } @@ -314,7 +314,7 @@ public class NotificationInfoTest extends SysuiTestCase { eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true, - false, IMPORTANCE_DEFAULT); + false, IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -323,7 +323,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -332,7 +332,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_BlockButton() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final View block = mNotificationInfo.findViewById(R.id.int_block); final View minimize = mNotificationInfo.findViewById(R.id.block_or_minimize); assertEquals(VISIBLE, block.getVisibility()); @@ -343,7 +343,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_BlockButton_BlockHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT); + true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT, true); final View block = mNotificationInfo.findViewById(R.id.block); final View interruptivenessSettings = mNotificationInfo.findViewById( R.id.interruptiveness_settings); @@ -356,7 +356,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); assertEquals(VISIBLE, silent.getVisibility()); assertEquals( @@ -368,7 +368,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); assertEquals(VISIBLE, silent.getVisibility()); assertEquals( @@ -381,7 +381,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, alert.getVisibility()); assertEquals( @@ -393,7 +393,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, alert.getVisibility()); assertEquals( @@ -405,7 +405,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, silent.getVisibility()); @@ -421,7 +421,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); final TextView silent = mNotificationInfo.findViewById(R.id.int_silent); final TextView alert = mNotificationInfo.findViewById(R.id.int_alert); assertEquals(VISIBLE, silent.getVisibility()); @@ -437,7 +437,7 @@ public class NotificationInfoTest extends SysuiTestCase { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final View block = mNotificationInfo.findViewById(R.id.block); final View interruptivenessSettings = mNotificationInfo.findViewById( R.id.interruptiveness_settings); @@ -455,7 +455,7 @@ public class NotificationInfoTest extends SysuiTestCase { (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); latch.countDown(); - }, null, true, false, IMPORTANCE_DEFAULT); + }, null, true, false, IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -467,7 +467,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -479,7 +479,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); - }, null, false, false, IMPORTANCE_DEFAULT); + }, null, false, false, IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -488,11 +488,11 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, (View v, NotificationChannel c, int appUid) -> { - }, null, true, false, IMPORTANCE_DEFAULT); + }, null, true, false, IMPORTANCE_DEFAULT, true); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertEquals(View.VISIBLE, settingsButton.getVisibility()); } @@ -501,7 +501,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testLogBlockingHelperCounter_logGutsViewDisplayed() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent"); verify(mMetricsLogger).write(argThat(logMaker -> logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER @@ -513,7 +513,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true, - true, true, IMPORTANCE_DEFAULT); + true, true, IMPORTANCE_DEFAULT, true); mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent"); verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1)); } @@ -526,7 +526,7 @@ public class NotificationInfoTest extends SysuiTestCase { (View v, NotificationChannel c, int appUid) -> { assertEquals(null, c); latch.countDown(); - }, null, true, true, IMPORTANCE_DEFAULT); + }, null, true, true, IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.info).performClick(); // Verify that listener was triggered. @@ -539,7 +539,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true, IMPORTANCE_DEFAULT); + null, true, true, IMPORTANCE_DEFAULT, true); final TextView channelNameView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, channelNameView.getVisibility()); @@ -550,7 +550,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true, IMPORTANCE_DEFAULT); + null, true, true, IMPORTANCE_DEFAULT, true); final TextView blockView = mNotificationInfo.findViewById(R.id.block); assertEquals(GONE, blockView.getVisibility()); } @@ -559,7 +559,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testbindNotification_BlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false, - true, true, IMPORTANCE_DEFAULT); + true, true, IMPORTANCE_DEFAULT, true); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText()); @@ -569,7 +569,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), @@ -580,7 +580,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -591,7 +591,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); mTestableLooper.processAllMessages(); @@ -605,7 +605,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); mTestableLooper.processAllMessages(); @@ -619,7 +619,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); mTestableLooper.processAllMessages(); @@ -633,7 +633,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); mTestableLooper.processAllMessages(); @@ -647,7 +647,7 @@ public class NotificationInfoTest extends SysuiTestCase { int originalImportance = mNotificationChannel.getImportance(); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -662,7 +662,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.handleCloseControls(true, false); @@ -680,7 +680,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , - true, false /* isNonblockable */, IMPORTANCE_DEFAULT + true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false ); mNotificationInfo.findViewById(R.id.int_block).performClick(); @@ -702,7 +702,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */, - true, false /* isNonblockable */, IMPORTANCE_DEFAULT + true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false ); mNotificationInfo.findViewById(R.id.int_block).performClick(); @@ -724,7 +724,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT); + true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); NotificationGuts guts = spy(new NotificationGuts(mContext, null)); when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); @@ -752,7 +752,7 @@ public class NotificationInfoTest extends SysuiTestCase { 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT); + true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); NotificationGuts guts = spy(new NotificationGuts(mContext, null)); when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); @@ -781,7 +781,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */ , false /* isNonblockable */, true /* isForBlockingHelper */, true, true /* isUserSentimentNegative */, /* isNoisy */ - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.handleCloseControls(true /* save */, false /* force */); @@ -800,7 +800,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */, true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT); + true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.block).performClick(); mTestableLooper.processAllMessages(); @@ -823,7 +823,7 @@ public class NotificationInfoTest extends SysuiTestCase { true /* isForBlockingHelper */, true, false /* isUserSentimentNegative */, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); NotificationGuts guts = mock(NotificationGuts.class); doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean()); mNotificationInfo.setGutsParent(guts); @@ -838,7 +838,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -852,7 +852,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -888,7 +888,8 @@ public class NotificationInfoTest extends SysuiTestCase { false /* isNonblockable */, true /* isForBlockingHelper */, true /* isUserSentimentNegative */, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, + false); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -913,7 +914,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -928,7 +929,7 @@ public class NotificationInfoTest extends SysuiTestCase { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -949,7 +950,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.handleCloseControls(true, false); @@ -967,7 +968,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -988,7 +989,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -1006,7 +1007,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); waitForUndoButton(); @@ -1027,7 +1028,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); waitForUndoButton(); @@ -1049,7 +1050,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); waitForUndoButton(); @@ -1071,7 +1072,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW); + IMPORTANCE_LOW, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); waitForUndoButton(); @@ -1092,7 +1093,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -1108,7 +1109,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -1125,7 +1126,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { - }, null, null, true, true, IMPORTANCE_DEFAULT); + }, null, null, true, true, IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); mTestableLooper.processAllMessages(); @@ -1143,7 +1144,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { saveImportance.run(); - }, null, null, true, false, IMPORTANCE_DEFAULT + }, null, null, true, false, IMPORTANCE_DEFAULT, false ); mNotificationInfo.findViewById(R.id.int_block).performClick(); @@ -1170,7 +1171,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -1183,7 +1184,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -1196,7 +1197,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, true); mNotificationInfo.findViewById(R.id.int_silent).performClick(); waitForUndoButton(); @@ -1210,7 +1211,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_alert).performClick(); waitForUndoButton(); @@ -1224,7 +1225,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); @@ -1236,7 +1237,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT); + IMPORTANCE_DEFAULT, false); mNotificationInfo.findViewById(R.id.int_block).performClick(); waitForUndoButton(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 736f3840b91a..ae70b01cd35c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -352,7 +352,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { RETURNS_DEEP_STUBS); String key = Integer.toString(i); when(row.getStatusBarNotification().getKey()).thenReturn(key); - when(mNotificationData.isHighPriority(row.getStatusBarNotification())).thenReturn(true); + when(row.getEntry().isHighPriority()).thenReturn(true); when(mStackScroller.getChildAt(i)).thenReturn(row); } @@ -368,8 +368,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { RETURNS_DEEP_STUBS); String key = Integer.toString(i); when(row.getStatusBarNotification().getKey()).thenReturn(key); - when(mNotificationData.isHighPriority(row.getStatusBarNotification())) - .thenReturn(false); + when(row.getEntry().isHighPriority()).thenReturn(false); when(mStackScroller.getChildAt(i)).thenReturn(row); } @@ -385,8 +384,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { RETURNS_DEEP_STUBS); String key = Integer.toString(i); when(row.getStatusBarNotification().getKey()).thenReturn(key); - when(mNotificationData.isHighPriority(row.getStatusBarNotification())) - .thenReturn(i < 3); + when(row.getEntry().isHighPriority()).thenReturn(i < 3); when(mStackScroller.getChildAt(i)).thenReturn(row); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index c0f7f0ce217f..1ded835e9651 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -85,7 +85,7 @@ public class AutoTileManagerTest extends SysuiTestCase { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( - ColorDisplayController.AUTO_MODE_TWILIGHT); + ColorDisplayManager.AUTO_MODE_TWILIGHT); verify(mQsTileHost).addTile("night"); } @@ -95,7 +95,7 @@ public class AutoTileManagerTest extends SysuiTestCase { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( - ColorDisplayController.AUTO_MODE_CUSTOM); + ColorDisplayManager.AUTO_MODE_CUSTOM_TIME); verify(mQsTileHost).addTile("night"); } @@ -105,7 +105,7 @@ public class AutoTileManagerTest extends SysuiTestCase { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( - ColorDisplayController.AUTO_MODE_DISABLED); + ColorDisplayManager.AUTO_MODE_DISABLED); verify(mQsTileHost, never()).addTile("night"); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index b7b95ef3ff6a..3b98f0ca8ce2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -51,6 +51,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { private NotificationStackScrollLayout mNotificationStackScrollLayout; @Mock private KeyguardStatusView mKeyguardStatusView; + @Mock + private KeyguardStatusBarView mKeyguardStatusBar; private NotificationPanelView mNotificationPanelView; @Before @@ -93,6 +95,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { super(NotificationPanelViewTest.this.mContext, null); mNotificationStackScroller = mNotificationStackScrollLayout; mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView; + mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar; } } } diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index d1aa84fe3133..73fcb0150a9e 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6780,22 +6780,22 @@ message MetricsEvent { CONVERSATION_ACTIONS = 1615; // ACTION: Actions from a text classifier are shown to user. - // CATEGORY: CONVERSATION_ACTIONS + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN = 1616; - // ACTION: Event time of a text classifier event in unix timestamp. - // CATEGORY: CONVERSATION_ACTIONS, LANGUAGE_DETECTION + // FIELD: Event time of a text classifier event in unix timestamp. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q FIELD_TEXT_CLASSIFIER_EVENT_TIME = 1617; // ACTION: Users compose their own replies instead of using suggested ones. - // CATEGORY: CONVERSATION_ACTIONS + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q ACTION_TEXT_CLASSIFIER_MANUAL_REPLY = 1618; // ACTION: Text classifier generates an action. - // CATEGORY: CONVERSATION_ACTIONS + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS // OS: Q ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED = 1619; @@ -6840,7 +6840,7 @@ message MetricsEvent { // OPEN: Settings > Display > Adaptive sleep // OS: Q SETTINGS_ADAPTIVE_SLEEP = 1628; - + // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION. // The UI location of the notification containing the smart suggestions. // This is a NotificationLocation object (see the NotificationLocation @@ -6870,6 +6870,40 @@ message MetricsEvent { // OS: Q DIALOG_AWARE_DISABLE = 1633; + // FIELD: Session ID of TextClassifierEvent. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_SESSION_ID = 1634; + + // FIELD: First entity type. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE = 1635; + // FIELD: Second entity type. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE = 1636; + + // FIELD: Third entity type. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE = 1637; + + // FIELD: Score of the suggestion. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_SCORE = 1638; + + // FIELD: widget type, e.g: notification, textview + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_WIDGET_TYPE = 1639; + + // FIELD: version of the widget. + // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS + // OS: Q + FIELD_TEXT_CLASSIFIER_WIDGET_VERSION = 1640; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index 12f666e10bce..c063e82766ed 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -965,6 +965,10 @@ message StaEvent { // NetworkAgent Wifi usability score of connected wifi optional int32 last_wifi_usability_score = 15 [default = -1]; + + // Prediction horizon (in second) of Wifi usability score provided by external + // system app + optional int32 last_prediction_horizon_sec = 16 [default = -1]; } // Wi-Fi Aware metrics @@ -1682,6 +1686,10 @@ message WifiIsUnusableEvent { // NetworkAgent wifi usability score of connected wifi. // Defaults to -1 if the score was never set. optional int32 last_wifi_usability_score = 11 [default = -1]; + + // Prediction horizon (in second) of Wifi usability score provided by external + // system app + optional int32 last_prediction_horizon_sec = 12 [default = -1]; } message PasspointProfileTypeCount { @@ -1814,6 +1822,10 @@ message WifiUsabilityStatsEntry { // The total number of beacons received from the last radio chip reset optional int64 total_beacon_rx = 22; + + // Prediction horizon (in second) of Wifi usability score provided by external + // system app + optional int32 prediction_horizon_sec = 23; } message WifiUsabilityStats { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index bcff4e0a90f1..303230b00c6f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -402,7 +402,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo MagnificationGestureHandler magnificationGestureHandler = new MagnificationGestureHandler(mContext, mAms.getMagnificationController(), - detectControlGestures, triggerable); + detectControlGestures, triggerable, displayId); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); } diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 49db488bc740..2fbaee65864a 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -43,7 +43,6 @@ import android.util.Log; import android.util.MathUtils; import android.util.Slog; import android.util.TypedValue; -import android.view.Display; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; @@ -149,6 +148,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; + private final int mDisplayId; + private final Queue<MotionEvent> mDebugInputEventHistory; private final Queue<MotionEvent> mDebugOutputEventHistory; @@ -162,11 +163,13 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some * external shortcut invoking {@link #notifyShortcutTriggered}, * {@code false} if it should ignore such triggers. + * @param displayId The logical display id. */ public MagnificationGestureHandler(Context context, MagnificationController magnificationController, boolean detectTripleTap, - boolean detectShortcutTrigger) { + boolean detectShortcutTrigger, + int displayId) { if (DEBUG_ALL) { Log.i(LOG_TAG, "MagnificationGestureHandler(detectTripleTap = " + detectTripleTap @@ -174,6 +177,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } mMagnificationController = magnificationController; + mDisplayId = displayId; mDelegatingState = new DelegatingState(); mDetectingState = new DetectingState(context); @@ -259,8 +263,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { void notifyShortcutTriggered() { if (mDetectShortcutTrigger) { - // TODO: multi-display support for magnification gesture handler - boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY, + boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId, /* animate */ true); if (wasMagnifying) { clearAndTransitionToStateDetecting(); @@ -422,8 +425,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX + " scrollY: " + distanceY); } - // TODO: multi-display support for magnification gesture handler - mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX, + mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX, distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); return /* event consumed: */ true; } @@ -440,8 +442,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { return mScaling; } - // TODO: multi-display support for magnification gesture handler - final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY); + final float initialScale = mMagnificationController.getScale(mDisplayId); final float targetScale = initialScale * detector.getScaleFactor(); // Don't allow a gesture to move the user further outside the @@ -463,8 +464,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final float pivotX = detector.getFocusX(); final float pivotY = detector.getFocusY(); if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x"); - // TODO: multi-display support for magnification gesture handler - mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false, + mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); return /* handled: */ true; } @@ -524,10 +524,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } final float eventX = event.getX(); final float eventY = event.getY(); - // TODO: multi-display support for magnification gesture handler if (mMagnificationController.magnificationRegionContains( - Display.DEFAULT_DISPLAY, eventX, eventY)) { - mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY, + mDisplayId, eventX, eventY)) { + mMagnificationController.setCenter(mDisplayId, eventX, eventY, /* animate */ mLastMoveOutsideMagnifiedRegion, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); mLastMoveOutsideMagnifiedRegion = false; @@ -665,9 +664,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - // TODO: multi-display support for magnification gesture handler if (!mMagnificationController.magnificationRegionContains( - Display.DEFAULT_DISPLAY, event.getX(), event.getY())) { + mDisplayId, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); @@ -684,8 +682,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay // to ensure reachability of // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) - // TODO: multi-display support for magnification gesture handler - || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { + || mMagnificationController.isMagnifying(mDisplayId)) { afterMultiTapTimeoutTransitionToDelegatingState(); @@ -697,8 +694,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } break; case ACTION_POINTER_DOWN: { - // TODO: multi-display support for magnification gesture handler - if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { + if (mMagnificationController.isMagnifying(mDisplayId)) { transitionTo(mPanningScalingState); clear(); } else { @@ -727,9 +723,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); - // TODO: multi-display support for magnification gesture handler if (!mMagnificationController.magnificationRegionContains( - Display.DEFAULT_DISPLAY, event.getX(), event.getY())) { + mDisplayId, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); @@ -880,8 +875,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { clear(); // Toggle zoom - // TODO: multi-display support for magnification gesture handler - if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { + if (mMagnificationController.isMagnifying(mDisplayId)) { zoomOff(); } else { zoomOn(up.getX(), up.getY()); @@ -893,9 +887,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()"); clear(); - // TODO: multi-display support for magnification gesture handler mViewportDraggingState.mZoomedInBeforeDrag = - mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY); + mMagnificationController.isMagnifying(mDisplayId); zoomOn(down.getX(), down.getY()); @@ -922,8 +915,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")"); mShortcutTriggered = state; - // TODO: multi-display support for magnification gesture handler - mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state); + mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state); } /** @@ -958,8 +950,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final float scale = MathUtils.constrain( mMagnificationController.getPersistedScale(), MIN_SCALE, MAX_SCALE); - // TODO: multi-display support for magnification gesture handler - mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY, + mMagnificationController.setScaleAndCenter(mDisplayId, scale, centerX, centerY, /* animate */ true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); @@ -967,8 +958,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private void zoomOff() { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()"); - // TODO: multi-display support for magnification gesture handler - mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true); + mMagnificationController.reset(mDisplayId, /* animate */ true); } private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) { @@ -990,6 +980,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) + ", mMagnificationController=" + mMagnificationController + + ", mDisplayId=" + mDisplayId + '}'; } diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index 8ffaddefd3ef..65e31f3acf14 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -435,7 +435,7 @@ class TouchExplorer extends BaseEventStreamTransformation MotionEvent click_event = MotionEvent.obtain(event.getDownTime(), event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties, coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, - event.getSource(), event.getFlags()); + event.getSource(), event.getDisplayId(), event.getFlags()); final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS); sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus); click_event.recycle(); @@ -1029,7 +1029,7 @@ class TouchExplorer extends BaseEventStreamTransformation event.getEventTime(), event.getAction(), event.getPointerCount(), props, coords, event.getMetaState(), event.getButtonState(), 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), - event.getSource(), event.getFlags()); + event.getSource(), event.getDisplayId(), event.getFlags()); } /** diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 4f58d7920d74..303734a4043c 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -18,6 +18,7 @@ package com.android.server.backup; import static com.android.server.backup.BackupManagerService.TAG; +import android.Manifest; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; @@ -41,9 +42,10 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.provider.Settings; +import android.os.UserManager; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import java.io.File; @@ -75,19 +77,25 @@ import java.io.PrintWriter; * system user is unlocked before any other users. */ public class Trampoline extends IBackupManager.Stub { - // When this file is present, the backup service is inactive. + /** + * Name of file that disables the backup service. If this file exists, then backup is disabled + * for all users. + */ private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; + /** + * Name of file for non-system users that enables the backup service for the user. Backup is + * disabled by default in non-system users. + */ + private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated"; + // Product-level suppression of backup/restore. private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; private static final String BACKUP_THREAD = "backup"; - /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */ - private static final int MULTI_USER_DISABLED = 0; - private static final int MULTI_USER_ENABLED = 1; - private final Context mContext; + private final UserManager mUserManager; private final boolean mGlobalDisable; // Lock to write backup suppress files. @@ -104,20 +112,13 @@ public class Trampoline extends IBackupManager.Stub { mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + mUserManager = UserManager.get(context); } protected boolean isBackupDisabled() { return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); } - private boolean isMultiUserEnabled() { - return Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.BACKUP_MULTI_USER_ENABLED, - MULTI_USER_DISABLED) - == MULTI_USER_ENABLED; - } - protected int binderGetCallingUserId() { return Binder.getCallingUserHandle().getIdentifier(); } @@ -126,21 +127,65 @@ public class Trampoline extends IBackupManager.Stub { return Binder.getCallingUid(); } - protected File getSuppressFileForUser(int userId) { - return new File(UserBackupManagerFiles.getBaseStateDir(userId), + /** Stored in the system user's directory. */ + protected File getSuppressFileForSystemUser() { + return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), BACKUP_SUPPRESS_FILENAME); } - protected void createBackupSuppressFileForUser(int userId) throws IOException { - synchronized (mStateLock) { - getSuppressFileForUser(userId).getParentFile().mkdirs(); - getSuppressFileForUser(userId).createNewFile(); + /** Stored in the system user's directory and the file is indexed by the user it refers to. */ + protected File getActivatedFileForNonSystemUser(int userId) { + return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), + BACKUP_ACTIVATED_FILENAME + "-" + userId); + } + + private void createFile(File file) throws IOException { + if (file.exists()) { + return; + } + + file.getParentFile().mkdirs(); + if (!file.createNewFile()) { + Slog.w(TAG, "Failed to create file " + file.getPath()); } } - private void deleteBackupSuppressFileForUser(int userId) { - if (!getSuppressFileForUser(userId).delete()) { - Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId); + private void deleteFile(File file) { + if (!file.exists()) { + return; + } + + if (!file.delete()) { + Slog.w(TAG, "Failed to delete file " + file.getPath()); + } + } + + /** + * Deactivates the backup service for user {@code userId}. If this is the system user, it + * creates a suppress file which disables backup for all users. If this is a non-system user, it + * only deactivates backup for that user by deleting its activate file. + */ + @GuardedBy("mStateLock") + private void deactivateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + createFile(getSuppressFileForSystemUser()); + } else { + deleteFile(getActivatedFileForNonSystemUser(userId)); + } + } + + /** + * Enables the backup service for user {@code userId}. If this is the system user, it deletes + * the suppress file. If this is a non-system user, it creates the user's activate file. Note, + * deleting the suppress file does not automatically enable backup for non-system users, they + * need their own activate file in order to participate in the service. + */ + @GuardedBy("mStateLock") + private void activateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + deleteFile(getSuppressFileForSystemUser()); + } else { + createFile(getActivatedFileForNonSystemUser(userId)); } } @@ -148,24 +193,31 @@ public class Trampoline extends IBackupManager.Stub { // admin (device owner or profile owner). private boolean isUserReadyForBackup(int userId) { return mService != null && mService.getServiceUsers().get(userId) != null - && !isBackupSuppressedForUser(userId); + && isBackupActivatedForUser(userId); } - private boolean isBackupSuppressedForUser(int userId) { - // If backup is disabled for system user, it's disabled for all other users on device. - if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) { - return true; - } - if (userId != UserHandle.USER_SYSTEM) { - return getSuppressFileForUser(userId).exists(); + /** + * Backup is activated for the system user if the suppress file does not exist. Backup is + * activated for non-system users if the suppress file does not exist AND the user's activated + * file exists. + */ + private boolean isBackupActivatedForUser(int userId) { + if (getSuppressFileForSystemUser().exists()) { + return false; } - return false; + + return userId == UserHandle.USER_SYSTEM + || getActivatedFileForNonSystemUser(userId).exists(); } protected Context getContext() { return mContext; } + protected UserManager getUserManager() { + return mUserManager; + } + protected BackupManagerService createBackupManagerService() { return new BackupManagerService(mContext, this, mHandlerThread); } @@ -198,23 +250,17 @@ public class Trampoline extends IBackupManager.Stub { /** * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked. - * Starts the backup service for this user if it's the system user or if the service supports - * multi-user. Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time - * low. + * Starts the backup service for this user if backup is active for this user. Offloads work onto + * the handler thread {@link #mHandlerThread} to keep unlock time low. */ void unlockUser(int userId) { - if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) { - Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId); - return; - } - postToHandler(() -> startServiceForUser(userId)); } private void startServiceForUser(int userId) { // We know that the user is unlocked here because it is called from setBackupServiceActive // and unlockUser which have these guarantees. So we can check if the file exists. - if (mService != null && !isBackupSuppressedForUser(userId)) { + if (mService != null && isBackupActivatedForUser(userId)) { Slog.i(TAG, "Starting service for user: " + userId); mService.startServiceForUser(userId); } @@ -225,11 +271,6 @@ public class Trampoline extends IBackupManager.Stub { * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low. */ void stopUser(int userId) { - if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) { - Slog.i(TAG, "Multi-user disabled, cannot stop service for user: " + userId); - return; - } - postToHandler( () -> { if (mService != null) { @@ -240,45 +281,63 @@ public class Trampoline extends IBackupManager.Stub { } /** - * Only privileged callers should be changing the backup state. This method only acts on {@link - * UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the - * system user also deactivates backup in all users. - * - * This call will only work if the calling {@code userID} is unlocked. + * The system user and managed profiles can only be acted on by callers in the system or root + * processes. Other users can be acted on by callers who have both android.permission.BACKUP and + * android.permission.INTERACT_ACROSS_USERS_FULL permissions. */ - public void setBackupServiceActive(int userId, boolean makeActive) { - int caller = binderGetCallingUid(); - if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { - throw new SecurityException("No permission to configure backup activity"); + private void enforcePermissionsOnUser(int userId) throws SecurityException { + boolean isRestrictedUser = + userId == UserHandle.USER_SYSTEM + || getUserManager().getUserInfo(userId).isManagedProfile(); + + if (isRestrictedUser) { + int caller = binderGetCallingUid(); + if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { + throw new SecurityException("No permission to configure backup activity"); + } + } else { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.BACKUP, "No permission to configure backup activity"); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "No permission to configure backup activity"); } + } + + /** + * Only privileged callers should be changing the backup state. Deactivating backup in the + * system user also deactivates backup in all users. We are not guaranteed that {@code userId} + * is unlocked at this point yet, so handle both cases. + */ + public void setBackupServiceActive(int userId, boolean makeActive) { + enforcePermissionsOnUser(userId); if (mGlobalDisable) { Slog.i(TAG, "Backup service not supported"); return; } - if (userId != UserHandle.USER_SYSTEM) { - Slog.i(TAG, "Cannot set backup service activity for non-system user: " + userId); - return; - } - - if (makeActive == isBackupServiceActive(userId)) { - Slog.i(TAG, "No change in backup service activity"); - return; - } - synchronized (mStateLock) { Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active"); if (makeActive) { if (mService == null) { mService = createBackupManagerService(); } - deleteBackupSuppressFileForUser(userId); - startServiceForUser(userId); + try { + activateBackupForUserLocked(userId); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service activity"); + } + + // If the user is unlocked, we can start the backup service for it. Otherwise we + // will start the service when the user is unlocked as part of its unlock callback. + if (getUserManager().isUserUnlocked(userId)) { + startServiceForUser(userId); + } } else { try { //TODO(b/121198006): what if this throws an exception? - createBackupSuppressFileForUser(userId); + deactivateBackupForUserLocked(userId); } catch (IOException e) { Slog.e(TAG, "Unable to persist backup service inactivity"); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8b32afbb73dc..14e235489b97 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -515,7 +515,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell // the world when it changes. - private final ProxyTracker mProxyTracker; + @VisibleForTesting + protected final ProxyTracker mProxyTracker; final private SettingsObserver mSettingsObserver; @@ -824,7 +825,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mPolicyManagerInternal = checkNotNull( LocalServices.getService(NetworkPolicyManagerInternal.class), "missing NetworkPolicyManagerInternal"); - mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED); + mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); mKeyStore = KeyStore.getInstance(); @@ -990,6 +991,11 @@ public class ConnectivityService extends IConnectivityManager.Stub deps); } + @VisibleForTesting + protected ProxyTracker makeProxyTracker() { + return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED); + } + private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); @@ -3724,20 +3730,46 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Returns information about the proxy a certain network is using. If given a null network, it + * it will return the proxy for the bound network for the caller app or the default proxy if + * none. + * + * @param network the network we want to get the proxy information for. + * @return Proxy information if a network has a proxy configured, or otherwise null. + */ @Override public ProxyInfo getProxyForNetwork(Network network) { - if (network == null) return mProxyTracker.getDefaultProxy(); final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy(); if (globalProxy != null) return globalProxy; - if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null; - // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which - // caller may not have. + if (network == null) { + // Get the network associated with the calling UID. + final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(), + true); + if (activeNetwork == null) { + return null; + } + return getLinkPropertiesProxyInfo(activeNetwork); + } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) { + // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which + // caller may not have. + return getLinkPropertiesProxyInfo(network); + } + // No proxy info available if the calling UID does not have network access. + return null; + } + + @VisibleForTesting + protected boolean queryUserAccess(int uid, int netId) { + return NetworkUtils.queryUserAccess(uid, netId); + } + + private ProxyInfo getLinkPropertiesProxyInfo(Network network) { final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return null; synchronized (nai) { - final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy(); - if (proxyInfo == null) return null; - return new ProxyInfo(proxyInfo); + final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy(); + return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy); } } @@ -3761,11 +3793,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker.setDefaultProxy(proxy); } - // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy. - // This method gets called when any network changes proxy, but the broadcast only ever contains - // the default proxy (even if it hasn't changed). - // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network - // world where an app might be bound to a non-default network. + // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called + // when any network changes proxy. + // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a + // multi-network world where an app might be bound to a non-default network. private void updateProxy(LinkProperties newLp, LinkProperties oldLp) { ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy(); ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy(); @@ -5932,12 +5963,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } scheduleUnvalidatedPrompt(networkAgent); - if (networkAgent.isVPN()) { - // Temporarily disable the default proxy (not global). - mProxyTracker.setDefaultProxyEnabled(false); - // TODO: support proxy per network. - } - // Whether a particular NetworkRequest listen should cause signal strength thresholds to // be communicated to a particular NetworkAgent depends only on the network's immutable, // capabilities, so it only needs to be done once on initial connect, not every time the @@ -5956,10 +5981,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } else if (state == NetworkInfo.State.DISCONNECTED) { networkAgent.asyncChannel.disconnect(); if (networkAgent.isVPN()) { - mProxyTracker.setDefaultProxyEnabled(true); updateUids(networkAgent, networkAgent.networkCapabilities, null); } disconnectAndDestroyNetwork(networkAgent); + if (networkAgent.isVPN()) { + // As the active or bound network changes for apps, broadcast the default proxy, as + // apps may need to update their proxy data. This is called after disconnecting from + // VPN to make sure we do not broadcast the old proxy data. + // TODO(b/122649188): send the broadcast only to VPN users. + mProxyTracker.sendProxyBroadcast(); + } } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) || state == NetworkInfo.State.SUSPENDED) { // going into or coming out of SUSPEND: re-score and notify diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 89ff33810aa2..c064453360c5 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -46,9 +46,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; -import android.net.InetAddresses; import android.net.INetd; -import android.net.INetdUnsolicitedEventListener; import android.net.INetworkManagementEventObserver; import android.net.ITetheringStatsProvider; import android.net.InterfaceConfiguration; @@ -63,6 +61,7 @@ import android.net.RouteInfo; import android.net.TetherStatsParcel; import android.net.UidRange; import android.net.shared.NetdService; +import android.net.shared.NetworkObserverRegistry; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; @@ -207,16 +206,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub private INetd mNetdService; - private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener; + private NMSNetworkObserverRegistry mNetworkObserverRegistry; private IBatteryStats mBatteryStats; private final Thread mThread; private CountDownLatch mConnectedSignal = new CountDownLatch(1); - private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = - new RemoteCallbackList<>(); - private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); @GuardedBy("mTetheringStatsProviders") @@ -326,8 +322,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub mDaemonHandler = new Handler(FgThread.get().getLooper()); - mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); - // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -346,7 +340,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub mFgHandler = null; mThread = null; mServices = null; - mNetdUnsolicitedEventListener = null; + mNetworkObserverRegistry = null; } static NetworkManagementService create(Context context, String socket, SystemServices services) @@ -394,14 +388,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void registerObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - mObservers.register(observer); + mNetworkObserverRegistry.registerObserver(observer); } @Override public void unregisterObserver(INetworkManagementEventObserver observer) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - mObservers.unregister(observer); + mNetworkObserverRegistry.unregisterObserver(observer); } @FunctionalInterface @@ -409,127 +401,101 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void sendCallback(INetworkManagementEventObserver o) throws RemoteException; } - private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { - final int length = mObservers.beginBroadcast(); - try { - for (int i = 0; i < length; i++) { - try { - eventCallback.sendCallback(mObservers.getBroadcastItem(i)); - } catch (RemoteException | RuntimeException e) { - } - } - } finally { - mObservers.finishBroadcast(); + private class NMSNetworkObserverRegistry extends NetworkObserverRegistry { + NMSNetworkObserverRegistry(Context context, Handler handler, INetd netd) + throws RemoteException { + super(context, handler, netd); } - } - - /** - * Notify our observers of an interface status change - */ - private void notifyInterfaceStatusChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); - } - - /** - * Notify our observers of an interface link state change - * (typically, an Ethernet cable has been plugged-in or unplugged). - */ - private void notifyInterfaceLinkStateChanged(String iface, boolean up) { - invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); - } - - /** - * Notify our observers of an interface addition. - */ - private void notifyInterfaceAdded(String iface) { - invokeForAllObservers(o -> o.interfaceAdded(iface)); - } - - /** - * Notify our observers of an interface removal. - */ - private void notifyInterfaceRemoved(String iface) { - // netd already clears out quota and alerts for removed ifaces; update - // our sanity-checking state. - mActiveAlerts.remove(iface); - mActiveQuotas.remove(iface); - invokeForAllObservers(o -> o.interfaceRemoved(iface)); - } - - /** - * Notify our observers of a limit reached. - */ - private void notifyLimitReached(String limitName, String iface) { - invokeForAllObservers(o -> o.limitReached(limitName, iface)); - } - /** - * Notify our observers of a change in the data activity state of the interface - */ - private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos, - int uid, boolean fromRadio) { - final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); - if (isMobile) { - if (!fromRadio) { - if (mMobileActivityFromRadio) { - // If this call is not coming from a report from the radio itself, but we - // have previously received reports from the radio, then we will take the - // power state to just be whatever the radio last reported. - powerState = mLastPowerStateFromRadio; + /** + * Notify our observers of a change in the data activity state of the interface + */ + @Override + public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + int uid, boolean fromRadio) { + final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); + int powerState = isActive + ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + + if (isMobile) { + if (!fromRadio) { + if (mMobileActivityFromRadio) { + // If this call is not coming from a report from the radio itself, but we + // have previously received reports from the radio, then we will take the + // power state to just be whatever the radio last reported. + powerState = mLastPowerStateFromRadio; + } + } else { + mMobileActivityFromRadio = true; } - } else { - mMobileActivityFromRadio = true; - } - if (mLastPowerStateFromRadio != powerState) { - mLastPowerStateFromRadio = powerState; - try { - getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { + if (mLastPowerStateFromRadio != powerState) { + mLastPowerStateFromRadio = powerState; + try { + getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { + } } StatsLog.write_non_chained(StatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null, powerState); } - } - if (ConnectivityManager.isNetworkTypeWifi(type)) { - if (mLastPowerStateFromWifi != powerState) { - mLastPowerStateFromWifi = powerState; - try { - getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); - } catch (RemoteException e) { + if (ConnectivityManager.isNetworkTypeWifi(type)) { + if (mLastPowerStateFromWifi != powerState) { + mLastPowerStateFromWifi = powerState; + try { + getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); + } catch (RemoteException e) { + } } StatsLog.write_non_chained(StatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null, powerState); } - } - boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM - || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; - - if (!isMobile || fromRadio || !mMobileActivityFromRadio) { - // Report the change in data activity. We don't do this if this is a change - // on the mobile network, that is not coming from the radio itself, and we - // have previously seen change reports from the radio. In that case only - // the radio is the authority for the current state. - final boolean active = isActive; - invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( - Integer.toString(type), active, tsNanos)); - } + if (!isMobile || fromRadio || !mMobileActivityFromRadio) { + // Report the change in data activity. We don't do this if this is a change + // on the mobile network, that is not coming from the radio itself, and we + // have previously seen change reports from the radio. In that case only + // the radio is the authority for the current state. + final boolean active = isActive; + super.notifyInterfaceClassActivity(type, isActive, tsNanos, uid, fromRadio); + } - boolean report = false; - synchronized (mIdleTimerLock) { - if (mActiveIdleTimers.isEmpty()) { - // If there are no idle timers, we are not monitoring activity, so we - // are always considered active. - isActive = true; + boolean report = false; + synchronized (mIdleTimerLock) { + if (mActiveIdleTimers.isEmpty()) { + // If there are no idle timers, we are not monitoring activity, so we + // are always considered active. + isActive = true; + } + if (mNetworkActive != isActive) { + mNetworkActive = isActive; + report = isActive; + } } - if (mNetworkActive != isActive) { - mNetworkActive = isActive; - report = isActive; + if (report) { + reportNetworkActive(); } } - if (report) { - reportNetworkActive(); + + /** + * Notify our observers of an interface removal. + */ + @Override + public void notifyInterfaceRemoved(String iface) { + // netd already clears out quota and alerts for removed ifaces; update + // our sanity-checking state. + mActiveAlerts.remove(iface); + mActiveQuotas.remove(iface); + super.notifyInterfaceRemoved(iface); + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't need to post to mDaemonHandler because the only thing + // that notifyCleartextNetwork does is post to a handler + ActivityManager.getService().notifyCleartextNetwork(uid, + HexDump.hexStringToByteArray(hex)); } } @@ -558,7 +524,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } // No current code examines the interface parameter in a global alert. Just pass null. - mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null)); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached( + LIMIT_GLOBAL_ALERT, null)); } } @@ -590,10 +557,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub private void connectNativeNetdService() { mNetdService = mServices.getNetd(); try { - mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener); - if (DBG) Slog.d(TAG, "Register unsolicited event listener"); + mNetworkObserverRegistry = new NMSNetworkObserverRegistry( + mContext, mDaemonHandler, mNetdService); + if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry"); } catch (RemoteException | ServiceSpecificException e) { - Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e); + Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e); } } @@ -697,120 +665,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub } - /** - * Notify our observers of a new or updated interface address. - */ - private void notifyAddressUpdated(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressUpdated(iface, address)); - } - - /** - * Notify our observers of a deleted interface address. - */ - private void notifyAddressRemoved(String iface, LinkAddress address) { - invokeForAllObservers(o -> o.addressRemoved(iface, address)); - } - - /** - * Notify our observers of DNS server information received. - */ - private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { - invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); - } - - /** - * Notify our observers of a route change. - */ - private void notifyRouteChange(boolean updated, RouteInfo route) { - if (updated) { - invokeForAllObservers(o -> o.routeUpdated(route)); - } else { - invokeForAllObservers(o -> o.routeRemoved(route)); - } - } - - private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { - @Override - public void onInterfaceClassActivityChanged(boolean isActive, - int label, long timestamp, int uid) throws RemoteException { - final long timestampNanos; - if (timestamp <= 0) { - timestampNanos = SystemClock.elapsedRealtimeNanos(); - } else { - timestampNanos = timestamp; - } - mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, - isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH - : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, - timestampNanos, uid, false)); - } - - @Override - public void onQuotaLimitReached(String alertName, String ifName) - throws RemoteException { - mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); - } - - @Override - public void onInterfaceDnsServerInfo(String ifName, - long lifetime, String[] servers) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); - } - - @Override - public void onInterfaceAddressUpdated(String addr, - String ifName, int flags, int scope) throws RemoteException { - final LinkAddress address = new LinkAddress(addr, flags, scope); - mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); - } - - @Override - public void onInterfaceAddressRemoved(String addr, - String ifName, int flags, int scope) throws RemoteException { - final LinkAddress address = new LinkAddress(addr, flags, scope); - mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); - } - - @Override - public void onInterfaceAdded(String ifName) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); - } - - @Override - public void onInterfaceRemoved(String ifName) throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); - } - - @Override - public void onInterfaceChanged(String ifName, boolean up) - throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); - } - - @Override - public void onInterfaceLinkStateChanged(String ifName, boolean up) - throws RemoteException { - mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); - } - - @Override - public void onRouteChanged(boolean updated, - String route, String gateway, String ifName) throws RemoteException { - final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), - ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), - ifName); - mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); - } - - @Override - public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { - // Don't need to post to mDaemonHandler because the only thing - // that notifyCleartextNetwork does is post to a handler - ActivityManager.getService().notifyCleartextNetwork(uid, - HexDump.hexStringToByteArray(hex)); - } - } - // // Netd Callback handling // @@ -859,16 +713,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("added")) { - notifyInterfaceAdded(cooked[3]); + mNetworkObserverRegistry.notifyInterfaceAdded(cooked[3]); return true; } else if (cooked[2].equals("removed")) { - notifyInterfaceRemoved(cooked[3]); + mNetworkObserverRegistry.notifyInterfaceRemoved(cooked[3]); return true; } else if (cooked[2].equals("changed") && cooked.length == 5) { - notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); + mNetworkObserverRegistry.notifyInterfaceStatusChanged( + cooked[3], cooked[4].equals("up")); return true; } else if (cooked[2].equals("linkstate") && cooked.length == 5) { - notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); + mNetworkObserverRegistry.notifyInterfaceLinkStateChanged( + cooked[3], cooked[4].equals("up")); return true; } throw new IllegalStateException(errorMessage); @@ -882,7 +738,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } if (cooked[2].equals("alert")) { - notifyLimitReached(cooked[3], cooked[4]); + mNetworkObserverRegistry.notifyLimitReached(cooked[3], cooked[4]); return true; } throw new IllegalStateException(errorMessage); @@ -908,9 +764,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub timestampNanos = SystemClock.elapsedRealtimeNanos(); } boolean isActive = cooked[2].equals("active"); - notifyInterfaceClassActivity(Integer.parseInt(cooked[3]), - isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH - : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + mNetworkObserverRegistry.notifyInterfaceClassActivity( + Integer.parseInt(cooked[3]), isActive, timestampNanos, processUid, false); return true; // break; @@ -937,9 +792,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub } if (cooked[2].equals("updated")) { - notifyAddressUpdated(iface, address); + mNetworkObserverRegistry.notifyAddressUpdated(iface, address); } else { - notifyAddressRemoved(iface, address); + mNetworkObserverRegistry.notifyAddressRemoved(iface, address); } return true; // break; @@ -959,7 +814,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(errorMessage); } String[] servers = cooked[5].split(","); - notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers); + mNetworkObserverRegistry.notifyInterfaceDnsServerInfo( + cooked[3], lifetime, servers); } return true; // break; @@ -998,7 +854,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub InetAddress gateway = null; if (via != null) gateway = InetAddress.parseNumericAddress(via); RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev); - notifyRouteChange(cooked[2].equals("updated"), route); + mNetworkObserverRegistry.notifyRouteChange( + cooked[2].equals("updated"), route); return true; } catch (IllegalArgumentException e) {} } @@ -1461,9 +1318,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (ConnectivityManager.isNetworkTypeMobile(type)) { mNetworkActive = false; } - mDaemonHandler.post(() -> notifyInterfaceClassActivity(type, - DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, - SystemClock.elapsedRealtimeNanos(), -1, false)); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( + type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false)); } } @@ -1486,9 +1342,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(e); } mActiveIdleTimers.remove(iface); - mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type, - DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, - SystemClock.elapsedRealtimeNanos(), -1, false)); + mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity( + params.type, false /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, + false)); } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 9d810cd8f3ca..cec825fb9c00 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -112,6 +112,7 @@ import android.os.storage.StorageManagerInternal; import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; +import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; import android.sysprop.VoldProperties; @@ -463,6 +464,7 @@ class StorageManagerService extends IStorageManager.Stub = { "password", "default", "pattern", "pin" }; private final Context mContext; + private final ContentResolver mResolver; private volatile IVold mVold; private volatile IStoraged mStoraged; @@ -797,6 +799,14 @@ class StorageManagerService extends IStorageManager.Stub refreshIsolatedStorageSettings(); } }); + // For now, simply clone property when it changes + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.Storage.NAMESPACE, + mContext.getMainExecutor(), (namespace, name, value) -> { + if (DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED.equals(name)) { + Settings.Global.putString(mResolver, + Settings.Global.ISOLATED_STORAGE_REMOTE, value); + } + }); refreshIsolatedStorageSettings(); } @@ -1523,6 +1533,8 @@ class StorageManagerService extends IStorageManager.Stub SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false))); mContext = context; + mResolver = mContext.getContentResolver(); + mCallbacks = new Callbacks(FgThread.get().getLooper()); mLockPatternUtils = new LockPatternUtils(mContext); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index a96676e5fc5f..5d6c2f074f9d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -677,6 +677,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -1649,6 +1650,7 @@ public final class ActiveServices { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName); intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback); diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index dd2b33ab1179..d025d739055b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -16,13 +16,19 @@ package com.android.server.am; +import static android.provider.DeviceConfig.ActivityManager.KEY_MAX_CACHED_PROCESSES; + import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; +import android.app.ActivityThread; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.OnPropertyChangedListener; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Slog; @@ -34,7 +40,6 @@ import java.io.PrintWriter; final class ActivityManagerConstants extends ContentObserver { // Key names stored in the settings value. - private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time"; private static final String KEY_FGSERVICE_MIN_SHOWN_TIME = "fgservice_min_shown_time"; @@ -69,13 +74,6 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_PROCESS_START_ASYNC = "process_start_async"; static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time"; static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration"; - static final String KEY_USE_COMPACTION = "use_compaction"; - static final String KEY_COMPACT_ACTION_1 = "compact_action_1"; - static final String KEY_COMPACT_ACTION_2 = "compact_action_2"; - static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; - static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; - static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; - static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -106,13 +104,6 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_PROCESS_START_ASYNC = true; private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000; private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000; - private static final boolean DEFAULT_USE_COMPACTION = false; - public static final int DEFAULT_COMPACT_ACTION_1 = 1; - public static final int DEFAULT_COMPACT_ACTION_2 = 3; - public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000; - public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000; - public static final long DEFAULT_COMPACT_THROTTLE_3 = 500; - public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000; // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -232,23 +223,6 @@ final class ActivityManagerConstants extends ContentObserver { // this long. public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION; - // Use compaction for background apps. - public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION; - - // Action for compactAppSome. - public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1; - // Action for compactAppFull; - public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2; - - // How long we'll skip second compactAppSome after first compactAppSome - public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1; - // How long we'll skip compactAppSome after compactAppFull - public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2; - // How long we'll skip compactAppFull after compactAppSome - public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3; - // How long we'll skip second compactAppFull after first compactAppFull - public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4; - // Indicates whether the activity starts logging is enabled. // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED volatile boolean mFlagActivityStartsLoggingEnabled; @@ -295,10 +269,19 @@ final class ActivityManagerConstants extends ContentObserver { Settings.Global.getUriFor( Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED); + private final OnPropertyChangedListener mOnDeviceConfigChangedListener = + new OnPropertyChangedListener() { + @Override + public void onPropertyChanged(String namespace, String name, String value) { + if (KEY_MAX_CACHED_PROCESSES.equals(name)) { + updateMaxCachedProcesses(); + } + } + }; + public ActivityManagerConstants(ActivityManagerService service, Handler handler) { super(handler); mService = service; - updateMaxCachedProcesses(); } public void start(ContentResolver resolver) { @@ -309,6 +292,11 @@ final class ActivityManagerConstants extends ContentObserver { updateConstants(); updateActivityStartsLoggingEnabled(); updateBackgroundActivityStartsEnabled(); + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE, + ActivityThread.currentApplication().getMainExecutor(), + mOnDeviceConfigChangedListener); + updateMaxCachedProcesses(); + } public void setOverrideMaxCachedProcesses(int value) { @@ -347,8 +335,6 @@ final class ActivityManagerConstants extends ContentObserver { // with defaults. Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e); } - MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES, - DEFAULT_MAX_CACHED_PROCESSES); BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME, DEFAULT_BACKGROUND_SETTLE_TIME); FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME, @@ -406,13 +392,9 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_MEMORY_INFO_THROTTLE_TIME); TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION, DEFAULT_TOP_TO_FGS_GRACE_DURATION); - USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION); - COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1); - COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2); - COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1); - COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2); - COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3); - COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4); + + // For new flags that are intended for server-side experiments, please use the new + // DeviceConfig package. updateMaxCachedProcesses(); } @@ -429,8 +411,19 @@ final class ActivityManagerConstants extends ContentObserver { } private void updateMaxCachedProcesses() { - CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 - ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses; + String maxCachedProcessesFlag = DeviceConfig.getProperty( + DeviceConfig.ActivityManager.NAMESPACE, KEY_MAX_CACHED_PROCESSES); + try { + CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 + ? (TextUtils.isEmpty(maxCachedProcessesFlag) + ? DEFAULT_MAX_CACHED_PROCESSES : Integer.parseInt(maxCachedProcessesFlag)) + : mOverrideMaxCachedProcesses; + } catch (NumberFormatException e) { + // Bad flag value from Phenotype, revert to default. + Slog.e("ActivityManagerConstants", + "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e); + CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; + } CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES); // Note the trim levels do NOT depend on the override process limit, we want @@ -503,8 +496,6 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(MEMORY_INFO_THROTTLE_TIME); pw.print(" "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("="); pw.println(TOP_TO_FGS_GRACE_DURATION); - pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("="); - pw.println(USE_COMPACTION); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b24290f62385..eb643b670fcd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1607,19 +1607,8 @@ public class ActivityManagerService extends IActivityManager.Stub } } break; case UPDATE_HTTP_PROXY_MSG: { - ProxyInfo proxy = (ProxyInfo)msg.obj; - String host = ""; - String port = ""; - String exclList = ""; - Uri pacFileUrl = Uri.EMPTY; - if (proxy != null) { - host = proxy.getHost(); - port = Integer.toString(proxy.getPort()); - exclList = proxy.getExclusionListAsString(); - pacFileUrl = proxy.getPacFileUrl(); - } synchronized (ActivityManagerService.this) { - mProcessList.setAllHttpProxyLocked(host, port, exclList, pacFileUrl); + mProcessList.setAllHttpProxyLocked(); } } break; case PROC_START_TIMEOUT_MSG: { @@ -7228,6 +7217,7 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.installSystemProviders(); mDevelopmentSettingsObserver = new DevelopmentSettingsObserver(); SettingsToPropertiesMapper.start(mContext.getContentResolver()); + mOomAdjuster.initSettings(); // Now that the settings provider is published we can consider sending // in a rescue party. @@ -9340,6 +9330,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mConstants.dump(pw); + mOomAdjuster.dumpAppCompactorSettings(pw); pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -9739,6 +9730,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else if ("settings".equals(cmd)) { synchronized (this) { mConstants.dump(pw); + mOomAdjuster.dumpAppCompactorSettings(pw); } } else if ("services".equals(cmd) || "s".equals(cmd)) { if (dumpClient) { @@ -14656,8 +14648,7 @@ public class ActivityManagerService extends IActivityManager.Stub mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG); break; case Proxy.PROXY_CHANGE_ACTION: - ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO); - mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG)); break; case android.hardware.Camera.ACTION_NEW_PICTURE: case android.hardware.Camera.ACTION_NEW_VIDEO: diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java index fd402cc08c0c..bb55ec3100c0 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/AppCompactor.java @@ -16,34 +16,63 @@ package com.android.server.am; -import com.android.internal.annotations.GuardedBy; +import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4; +import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION; import android.app.ActivityManager; - +import android.app.ActivityThread; import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.Trace; - +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.OnPropertyChangedListener; +import android.text.TextUtils; import android.util.EventLog; import android.util.StatsLog; -import static android.os.Process.THREAD_PRIORITY_FOREGROUND; - +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.ServiceThread; import java.io.FileOutputStream; -import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; public final class AppCompactor { - /** - * Processes to compact. - */ - final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>(); + // Phenotype sends int configurations and we map them to the strings we'll use on device, + // preventing a weird string value entering the kernel. + private static final int COMPACT_ACTION_FILE_FLAG = 1; + private static final int COMPACT_ACTION_ANON_FLAG = 2; + private static final int COMPACT_ACTION_FULL_FLAG = 3; + private static final String COMPACT_ACTION_FILE = "file"; + private static final String COMPACT_ACTION_ANON = "anon"; + private static final String COMPACT_ACTION_FULL = "all"; + + // Defaults for phenotype flags. + @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500; + @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000; + + @VisibleForTesting + interface PropertyChangedCallbackForTest { + void onPropertyChanged(); + } + private PropertyChangedCallbackForTest mTestCallback; + + // Handler constants. static final int COMPACT_PROCESS_SOME = 1; static final int COMPACT_PROCESS_FULL = 2; static final int COMPACT_PROCESS_MSG = 1; @@ -57,70 +86,106 @@ public final class AppCompactor { */ final ServiceThread mCompactionThread; - final private Handler mCompactionHandler; - - final private ActivityManagerService mAm; - final private ActivityManagerConstants mConstants; - - final private String COMPACT_ACTION_FILE = "file"; - final private String COMPACT_ACTION_ANON = "anon"; - final private String COMPACT_ACTION_FULL = "all"; - - final private String compactActionSome; - final private String compactActionFull; - - final private long throttleSomeSome; - final private long throttleSomeFull; - final private long throttleFullSome; - final private long throttleFullFull; + private final ArrayList<ProcessRecord> mPendingCompactionProcesses = + new ArrayList<ProcessRecord>(); + private final ActivityManagerService mAm; + private final OnPropertyChangedListener mOnFlagsChangedListener = + new OnPropertyChangedListener() { + @Override + public void onPropertyChanged(String namespace, String name, String value) { + synchronized (mPhenotypeFlagLock) { + if (KEY_USE_COMPACTION.equals(name)) { + updateUseCompaction(); + } else if (KEY_COMPACT_ACTION_1.equals(name) + || KEY_COMPACT_ACTION_2.equals(name)) { + updateCompactionActions(); + } else if (KEY_COMPACT_THROTTLE_1.equals(name) + || KEY_COMPACT_THROTTLE_2.equals(name) + || KEY_COMPACT_THROTTLE_3.equals(name) + || KEY_COMPACT_THROTTLE_4.equals(name)) { + updateCompactionThrottles(); + } + } + if (mTestCallback != null) { + mTestCallback.onPropertyChanged(); + } + } + }; + + private final Object mPhenotypeFlagLock = new Object(); + + // Configured by phenotype. Updates from the server take effect immediately. + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting String mCompactActionSome = + compactActionIntToString(DEFAULT_COMPACT_ACTION_1); + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting String mCompactActionFull = + compactActionIntToString(DEFAULT_COMPACT_ACTION_2); + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4; + @GuardedBy("mPhenotypeFlagLock") + private boolean mUseCompaction = DEFAULT_USE_COMPACTION; + + // Handler on which compaction runs. + private Handler mCompactionHandler; public AppCompactor(ActivityManagerService am) { mAm = am; - mConstants = am.mConstants; - mCompactionThread = new ServiceThread("CompactionThread", THREAD_PRIORITY_FOREGROUND, true); - mCompactionThread.start(); - mCompactionHandler = new MemCompactionHandler(this); - - switch(mConstants.COMPACT_ACTION_1) { - case 1: - compactActionSome = COMPACT_ACTION_FILE; - break; - case 2: - compactActionSome = COMPACT_ACTION_ANON; - break; - case 3: - compactActionSome = COMPACT_ACTION_FULL; - break; - default: - compactActionSome = COMPACT_ACTION_FILE; - break; + } + + @VisibleForTesting + AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) { + this(am); + mTestCallback = callback; + } + + /** + * Reads phenotype config to determine whether app compaction is enabled or not and + * starts the background thread if necessary. + */ + public void init() { + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE, + ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener); + synchronized (mPhenotypeFlagLock) { + updateUseCompaction(); + updateCompactionActions(); + updateCompactionThrottles(); } + } - switch(mConstants.COMPACT_ACTION_2) { - case 1: - compactActionFull = COMPACT_ACTION_FILE; - break; - case 2: - compactActionFull = COMPACT_ACTION_ANON; - break; - case 3: - compactActionFull = COMPACT_ACTION_FULL; - break; - default: - compactActionFull = COMPACT_ACTION_FULL; - break; + /** + * Returns whether compaction is enabled. + */ + public boolean useCompaction() { + synchronized (mPhenotypeFlagLock) { + return mUseCompaction; } + } - throttleSomeSome = mConstants.COMPACT_THROTTLE_1; - throttleSomeFull = mConstants.COMPACT_THROTTLE_2; - throttleFullSome = mConstants.COMPACT_THROTTLE_3; - throttleFullFull = mConstants.COMPACT_THROTTLE_4; + @GuardedBy("mAm") + void dump(PrintWriter pw) { + pw.println("AppCompactor settings"); + synchronized (mPhenotypeFlagLock) { + pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction); + pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome); + pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull); + pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome); + pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull); + pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome); + pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull); + } } - // Must be called while holding AMS lock. - final void compactAppSome(ProcessRecord app) { + @GuardedBy("mAm") + void compactAppSome(ProcessRecord app) { app.reqCompactAction = COMPACT_PROCESS_SOME; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( @@ -128,8 +193,8 @@ public final class AppCompactor { COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } - // Must be called while holding AMS lock. - final void compactAppFull(ProcessRecord app) { + @GuardedBy("mAm") + void compactAppFull(ProcessRecord app) { app.reqCompactAction = COMPACT_PROCESS_FULL; mPendingCompactionProcesses.add(app); mCompactionHandler.sendMessage( @@ -137,97 +202,205 @@ public final class AppCompactor { COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } - final class MemCompactionHandler extends Handler { - AppCompactor mAc; - private MemCompactionHandler(AppCompactor ac) { - super(ac.mCompactionThread.getLooper()); - mAc = ac; + /** + * Reads the flag value from DeviceConfig to determine whether app compaction + * should be enabled, and starts/stops the compaction thread as needed. + */ + @GuardedBy("mPhenotypeFlagLock") + private void updateUseCompaction() { + String useCompactionFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION); + mUseCompaction = TextUtils.isEmpty(useCompactionFlag) + ? DEFAULT_USE_COMPACTION : Boolean.parseBoolean(useCompactionFlag); + if (mUseCompaction && !mCompactionThread.isAlive()) { + mCompactionThread.start(); + mCompactionHandler = new MemCompactionHandler(); + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateCompactionActions() { + String compactAction1Flag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1); + String compactAction2Flag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2); + + int compactAction1 = DEFAULT_COMPACT_ACTION_1; + try { + compactAction1 = TextUtils.isEmpty(compactAction1Flag) + ? DEFAULT_COMPACT_ACTION_1 : Integer.parseInt(compactAction1Flag); + } catch (NumberFormatException e) { + // Do nothing, leave default. + } + + int compactAction2 = DEFAULT_COMPACT_ACTION_2; + try { + compactAction2 = TextUtils.isEmpty(compactAction2Flag) + ? DEFAULT_COMPACT_ACTION_2 : Integer.parseInt(compactAction2Flag); + } catch (NumberFormatException e) { + // Do nothing, leave default. + } + + mCompactActionSome = compactActionIntToString(compactAction1); + mCompactActionFull = compactActionIntToString(compactAction2); + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateCompactionThrottles() { + boolean useThrottleDefaults = false; + String throttleSomeSomeFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1); + String throttleSomeFullFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2); + String throttleFullSomeFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3); + String throttleFullFullFlag = + DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4); + + if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag) + || TextUtils.isEmpty(throttleFullSomeFlag) + || TextUtils.isEmpty(throttleFullFullFlag)) { + // Set defaults for all if any are not set. + useThrottleDefaults = true; + } else { + try { + mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag); + mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag); + mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag); + mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag); + } catch (NumberFormatException e) { + useThrottleDefaults = true; + } + } + + if (useThrottleDefaults) { + mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1; + mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2; + mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3; + mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4; + } + } + + @VisibleForTesting + static String compactActionIntToString(int action) { + switch(action) { + case COMPACT_ACTION_FILE_FLAG: + return COMPACT_ACTION_FILE; + case COMPACT_ACTION_ANON_FLAG: + return COMPACT_ACTION_ANON; + case COMPACT_ACTION_FULL_FLAG: + return COMPACT_ACTION_FULL; + default: + return COMPACT_ACTION_FILE; + } + } + + private final class MemCompactionHandler extends Handler { + private MemCompactionHandler() { + super(mCompactionThread.getLooper()); } @Override public void handleMessage(Message msg) { switch (msg.what) { - case COMPACT_PROCESS_MSG: { - long start = SystemClock.uptimeMillis(); - ProcessRecord proc; - int pid; - String action; - final String name; - int pendingAction, lastCompactAction; - long lastCompactTime; - synchronized(mAc.mAm) { - proc = mAc.mPendingCompactionProcesses.remove(0); - - // don't compact if the process has returned to perceptible - if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { - return; + case COMPACT_PROCESS_MSG: { + long start = SystemClock.uptimeMillis(); + ProcessRecord proc; + int pid; + String action; + final String name; + int pendingAction, lastCompactAction; + long lastCompactTime; + synchronized (mAm) { + proc = mPendingCompactionProcesses.remove(0); + + // don't compact if the process has returned to perceptible + if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { + return; + } + + pid = proc.pid; + name = proc.processName; + pendingAction = proc.reqCompactAction; + lastCompactAction = proc.lastCompactAction; + lastCompactTime = proc.lastCompactTime; } - pid = proc.pid; - name = proc.processName; - pendingAction = proc.reqCompactAction; - lastCompactAction = proc.lastCompactAction; - lastCompactTime = proc.lastCompactTime; - } - if (pid == 0) { - // not a real process, either one being launched or one being killed - return; - } - - // basic throttling - // use the ActivityManagerConstants knobs to determine whether current/prevous - // compaction combo should be throtted or not - if (pendingAction == COMPACT_PROCESS_SOME) { - if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) || - (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) { + if (pid == 0) { + // not a real process, either one being launched or one being killed return; } - } else { - if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) || - (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) { - return; + + // basic throttling + // use the Phenotype flag knobs to determine whether current/prevous + // compaction combo should be throtted or not + + // Note that we explicitly don't take mPhenotypeFlagLock here as the flags + // should very seldom change, and taking the risk of using the wrong action is + // preferable to taking the lock for every single compaction action. + if (pendingAction == COMPACT_PROCESS_SOME) { + if ((lastCompactAction == COMPACT_PROCESS_SOME + && (start - lastCompactTime < mCompactThrottleSomeSome)) + || (lastCompactAction == COMPACT_PROCESS_FULL + && (start - lastCompactTime + < mCompactThrottleSomeFull))) { + return; + } + } else { + if ((lastCompactAction == COMPACT_PROCESS_SOME + && (start - lastCompactTime < mCompactThrottleFullSome)) + || (lastCompactAction == COMPACT_PROCESS_FULL + && (start - lastCompactTime + < mCompactThrottleFullFull))) { + return; + } } - } - try { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + - ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + - ": " + name); - long[] rssBefore = Process.getRss(pid); - FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); if (pendingAction == COMPACT_PROCESS_SOME) { - action = compactActionSome; + action = mCompactActionSome; } else { - action = compactActionFull; + action = mCompactActionFull; } - fos.write(action.getBytes()); - fos.close(); - long[] rssAfter = Process.getRss(pid); - long end = SystemClock.uptimeMillis(); - long time = end - start; - EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, - rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); - StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, - rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, - ActivityManager.processStateAmToProto(msg.arg2)); - synchronized(mAc.mAm) { - proc.lastCompactTime = end; - proc.lastCompactAction = pendingAction; + + try { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + + ": " + name); + long[] rssBefore = Process.getRss(pid); + FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); + fos.write(action.getBytes()); + fos.close(); + long[] rssAfter = Process.getRss(pid); + long end = SystemClock.uptimeMillis(); + long time = end - start; + EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); + StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, + ActivityManager.processStateAmToProto(msg.arg2)); + synchronized (mAm) { + proc.lastCompactTime = end; + proc.lastCompactAction = pendingAction; + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } catch (Exception e) { + // nothing to do, presumably the process died + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } catch (Exception e) { - // nothing to do, presumably the process died - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } - } } } - - } diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java index aabb5877764e..dc9a4bf1ad94 100644 --- a/services/core/java/com/android/server/am/BaseErrorDialog.java +++ b/services/core/java/com/android/server/am/BaseErrorDialog.java @@ -16,8 +16,6 @@ package com.android.server.am; -import com.android.internal.R; - import android.app.AlertDialog; import android.content.Context; import android.os.Handler; @@ -26,6 +24,8 @@ import android.view.KeyEvent; import android.view.WindowManager; import android.widget.Button; +import com.android.internal.R; + public class BaseErrorDialog extends AlertDialog { private static final int ENABLE_BUTTONS = 0; private static final int DISABLE_BUTTONS = 1; @@ -36,7 +36,7 @@ public class BaseErrorDialog extends AlertDialog { super(context, com.android.internal.R.style.Theme_DeviceDefault_Dialog_AppError); context.assertRuntimeOverlayThemable(); - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); WindowManager.LayoutParams attrs = getWindow().getAttributes(); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 353749f211c1..cdf6e0ede865 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -776,6 +776,7 @@ public final class BroadcastQueue { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName); intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index d3953b58296c..3d69aa8088f5 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -61,6 +61,8 @@ final class CoreSettingsObserver extends ContentObserver { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class); sGlobalSettingToTypeMap.put( Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class); + sGlobalSettingToTypeMap.put( + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, String.class); sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index be910d46a8ad..1e03f6c3ba5f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -134,10 +134,11 @@ public final class OomAdjuster { mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class); mConstants = mService.mConstants; - // mConstants can be null under test, which causes AppCompactor to crash - if (mConstants != null) { - mAppCompact = new AppCompactor(mService); - } + mAppCompact = new AppCompactor(mService); + } + + void initSettings() { + mAppCompact.init(); } /** @@ -1679,7 +1680,7 @@ public final class OomAdjuster { if (app.curAdj != app.setAdj) { // don't compact during bootup - if (mConstants.USE_COMPACTION && mService.mBooted) { + if (mAppCompact.useCompaction() && mService.mBooted) { // Perform a minor compaction when a perceptible app becomes the prev/home app // Perform a major compaction when any app enters cached // reminder: here, setAdj is previous state, curAdj is upcoming state @@ -2104,4 +2105,8 @@ public final class OomAdjuster { + " mNewNumServiceProcs=" + mNewNumServiceProcs); } + @GuardedBy("mService") + void dumpAppCompactorSettings(PrintWriter pw) { + mAppCompact.dump(pw); + } } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 55cca950a213..0b27a8ad77dc 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,6 +379,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub { if (userId == UserHandle.USER_CURRENT) { userId = controller.mUserController.getCurrentOrTargetUserId(); } + // temporarily allow receivers and services to open activities from background if the + // PendingIntent.send() caller was foreground at the time of sendInner() call + final boolean allowTrampoline = uid != callingUid + && controller.mAtmInternal.isUidForeground(callingUid); switch (key.type) { case ActivityManager.INTENT_SENDER_ACTIVITY: @@ -419,7 +423,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { uid, finalIntent, resolvedType, finishedReceiver, code, null, null, requiredPermission, options, (finishedReceiver != null), false, userId, - mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken)); + mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken) + || allowTrampoline); if (sent == ActivityManager.BROADCAST_SUCCESS) { sendFinish = false; } @@ -433,7 +438,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType, key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE, key.packageName, userId, - mAllowBgActivityStartsForServiceSender.contains(whitelistToken)); + mAllowBgActivityStartsForServiceSender.contains(whitelistToken) + || allowTrampoline); } catch (RuntimeException e) { Slog.w(TAG, "Unable to send startService intent", e); } catch (TransactionTooLargeException e) { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 64ee30af2f39..003ddd13f152 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2384,14 +2384,14 @@ public final class ProcessList { } @GuardedBy("mService") - void setAllHttpProxyLocked(String host, String port, String exclList, Uri pacFileUrl) { + void setAllHttpProxyLocked() { for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord r = mLruProcesses.get(i); // Don't dispatch to isolated processes as they can't access // ConnectivityManager and don't have network privileges anyway. if (r.thread != null && !r.isolated) { try { - r.thread.setHttpProxy(host, port, exclList, pacFileUrl); + r.thread.updateHttpProxy(); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update http proxy for: " + r.info.processName); diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java index fdddccd20714..a671287324af 100644 --- a/services/core/java/com/android/server/connectivity/ProxyTracker.java +++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java @@ -309,22 +309,4 @@ public class ProxyTracker { } } } - - /** - * Enable or disable the default proxy. - * - * This sets the flag for enabling/disabling the default proxy and sends the broadcast - * if applicable. - * @param enabled whether the default proxy should be enabled. - */ - public void setDefaultProxyEnabled(final boolean enabled) { - synchronized (mProxyLock) { - if (mDefaultProxyEnabled != enabled) { - mDefaultProxyEnabled = enabled; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(); - } - } - } - } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index c72c9ddf3f7a..62a1b036daa0 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -793,6 +793,8 @@ public class Vpn { } } + lp.setHttpProxy(mConfig.proxyInfo); + if (!allowIPv4) { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 5ed626342d31..e268e4458986 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -48,7 +48,6 @@ import android.os.Build; import android.os.Bundle; import android.os.FactoryTest; import android.os.IBinder; -import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -255,21 +254,6 @@ public final class ContentService extends IContentService.Stub { } } - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - try { - return super.onTransact(code, data, reply, flags); - } catch (RuntimeException e) { - // The content service only throws security exceptions, so let's - // log all others. - if (!(e instanceof SecurityException)) { - Slog.wtf(TAG, "Content Service Crash", e); - } - throw e; - } - } - /*package*/ ContentService(Context context, boolean factoryTest) { mContext = context; mFactoryTest = factoryTest; diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java index 3a58160cae8d..eb0ed0a62ebf 100644 --- a/services/core/java/com/android/server/display/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/ColorDisplayService.java @@ -16,6 +16,13 @@ package com.android.server.display; +import static android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME; +import static android.hardware.display.ColorDisplayManager.AUTO_MODE_DISABLED; +import static android.hardware.display.ColorDisplayManager.AUTO_MODE_TWILIGHT; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL; +import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION; @@ -40,13 +47,17 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.ColorSpace; import android.hardware.display.ColorDisplayManager; +import android.hardware.display.ColorDisplayManager.AutoMode; +import android.hardware.display.ColorDisplayManager.ColorMode; import android.hardware.display.IColorDisplayManager; +import android.hardware.display.Time; import android.net.Uri; import android.opengl.Matrix; import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Secure; import android.provider.Settings.System; @@ -55,7 +66,6 @@ import android.util.Slog; import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; - import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ColorDisplayController; @@ -65,7 +75,6 @@ import com.android.server.SystemService; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -103,59 +112,17 @@ public final class ColorDisplayService extends SystemService { private static final int MSG_APPLY_GLOBAL_SATURATION = 2; /** + * Return value if a setting has not been set. + */ + private static final int NOT_SET = -1; + + /** * Evaluator used to animate color matrix transitions. */ private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator(); - private final TintController mNightDisplayTintController = new TintController() { - - private float[] mMatrixNightDisplay = new float[16]; - private final float[] mColorTempCoefficients = new float[9]; - - /** - * Set coefficients based on whether the color matrix is linear or not. - */ - @Override - public void setUp(Context context, boolean needsLinear) { - final String[] coefficients = context.getResources().getStringArray(needsLinear - ? R.array.config_nightDisplayColorTemperatureCoefficients - : R.array.config_nightDisplayColorTemperatureCoefficientsNative); - for (int i = 0; i < 9 && i < coefficients.length; i++) { - mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]); - } - } - - @Override - public void setMatrix(int cct) { - if (mMatrixNightDisplay.length != 16) { - Slog.d(TAG, "The display transformation matrix must be 4x4"); - return; - } - - Matrix.setIdentityM(mMatrixNightDisplay, 0); - - final float squareTemperature = cct * cct; - final float red = squareTemperature * mColorTempCoefficients[0] - + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2]; - final float green = squareTemperature * mColorTempCoefficients[3] - + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5]; - final float blue = squareTemperature * mColorTempCoefficients[6] - + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8]; - mMatrixNightDisplay[0] = red; - mMatrixNightDisplay[5] = green; - mMatrixNightDisplay[10] = blue; - } - - @Override - public float[] getMatrix() { - return isActivated() ? mMatrixNightDisplay : MATRIX_IDENTITY; - } - - @Override - public int getLevel() { - return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; - } - }; + private final NightDisplayTintController mNightDisplayTintController = + new NightDisplayTintController(); private final TintController mDisplayWhiteBalanceTintController = new TintController() { // Three chromaticity coordinates per color: X, Y, and Z @@ -198,7 +165,7 @@ public final class ColorDisplayService extends SystemService { } float[] displayRedGreenBlueXYZ = - new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY]; + new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY]; float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) { displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]); @@ -209,10 +176,10 @@ public final class ColorDisplayService extends SystemService { } final ColorSpace.Rgb displayColorSpaceRGB = new ColorSpace.Rgb( - "Display Color Space", - displayRedGreenBlueXYZ, - displayWhiteXYZ, - 2.2f // gamma, unused for display white balance + "Display Color Space", + displayRedGreenBlueXYZ, + displayWhiteXYZ, + 2.2f // gamma, unused for display white balance ); float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; @@ -278,8 +245,8 @@ public final class ColorDisplayService extends SystemService { mCurrentColorTemperatureXYZ = ColorSpace.cctToIlluminantdXyz(cct); mChromaticAdaptationMatrix = - ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, - mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ); + ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, + mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ); // Convert the adaptation matrix to RGB space float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix, @@ -386,7 +353,7 @@ public final class ColorDisplayService extends SystemService { float saturation = saturationLevel * 0.1f; float desaturation = 1.0f - saturation; float[] luminance = {0.231f * desaturation, 0.715f * desaturation, - 0.072f * desaturation}; + 0.072f * desaturation}; mMatrixGlobalSaturation[0] = luminance[0] + saturation; mMatrixGlobalSaturation[1] = luminance[0]; mMatrixGlobalSaturation[2] = luminance[0]; @@ -421,7 +388,7 @@ public final class ColorDisplayService extends SystemService { * subtraction from 1. The last row represents a non-multiplied addition, see surfaceflinger's * ProgramCache for full implementation details. */ - private static final float[] MATRIX_INVERT_COLOR = new float[] { + private static final float[] MATRIX_INVERT_COLOR = new float[]{ 0.402f, -0.598f, -0.599f, 0f, -1.174f, -0.174f, -1.175f, 0f, -0.228f, -0.228f, 0.772f, 0f, @@ -445,7 +412,7 @@ public final class ColorDisplayService extends SystemService { public ColorDisplayService(Context context) { super(context); - mHandler = new TintHandler(Looper.getMainLooper()); + mHandler = new TintHandler(DisplayThread.get().getLooper()); } @Override @@ -548,23 +515,30 @@ public final class ColorDisplayService extends SystemService { if (setting != null) { switch (setting) { case Secure.NIGHT_DISPLAY_ACTIVATED: - onNightDisplayActivated(mNightDisplayController.isActivated()); + final boolean activated = isNightDisplayActivatedSetting(); + if (mNightDisplayTintController.isActivatedStateNotSet() + || mNightDisplayTintController.isActivated() != activated) { + mNightDisplayTintController.onActivated(activated); + } break; case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE: - onNightDisplayColorTemperatureChanged( - mNightDisplayController.getColorTemperature()); + final int temperature = getNightDisplayColorTemperatureSetting(); + if (mNightDisplayTintController.getColorTemperature() + != temperature) { + mNightDisplayTintController + .onColorTemperatureChanged(temperature); + } break; case Secure.NIGHT_DISPLAY_AUTO_MODE: - onNightDisplayAutoModeChanged( - mNightDisplayController.getAutoMode()); + onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal()); break; case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME: onNightDisplayCustomStartTimeChanged( - mNightDisplayController.getCustomStartTime()); + getNightDisplayCustomStartTimeInternal().getLocalTime()); break; case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME: onNightDisplayCustomEndTimeChanged( - mNightDisplayController.getCustomEndTime()); + getNightDisplayCustomEndTimeInternal().getLocalTime()); break; case System.DISPLAY_COLOR_MODE: onDisplayColorModeChanged(mNightDisplayController.getColorMode()); @@ -624,14 +598,14 @@ public final class ColorDisplayService extends SystemService { // Prepare the night display color transformation matrix. mNightDisplayTintController .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix()); - mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature()); + mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting()); // Initialize the current auto mode. - onNightDisplayAutoModeChanged(mNightDisplayController.getAutoMode()); + onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal()); - // Force the initialization current activated state. + // Force the initialization of the current saved activation state. if (mNightDisplayTintController.isActivatedStateNotSet()) { - onNightDisplayActivated(mNightDisplayController.isActivated()); + mNightDisplayTintController.onActivated(isNightDisplayActivatedSetting()); } } @@ -665,23 +639,6 @@ public final class ColorDisplayService extends SystemService { } } - private void onNightDisplayActivated(boolean activated) { - if (mNightDisplayTintController.isActivatedStateNotSet() - || mNightDisplayTintController.isActivated() != activated) { - Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display"); - - mNightDisplayTintController.setActivated(activated); - - if (mNightDisplayAutoMode != null) { - mNightDisplayAutoMode.onActivated(activated); - } - - updateDisplayWhiteBalanceStatus(); - - applyTint(mNightDisplayTintController, false); - } - } - private void onNightDisplayAutoModeChanged(int autoMode) { Slog.d(TAG, "onNightDisplayAutoModeChanged: autoMode=" + autoMode); @@ -690,9 +647,9 @@ public final class ColorDisplayService extends SystemService { mNightDisplayAutoMode = null; } - if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) { + if (autoMode == AUTO_MODE_CUSTOM_TIME) { mNightDisplayAutoMode = new CustomNightDisplayAutoMode(); - } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) { + } else if (autoMode == AUTO_MODE_TWILIGHT) { mNightDisplayAutoMode = new TwilightNightDisplayAutoMode(); } @@ -717,13 +674,8 @@ public final class ColorDisplayService extends SystemService { } } - private void onNightDisplayColorTemperatureChanged(int colorTemperature) { - mNightDisplayTintController.setMatrix(colorTemperature); - applyTint(mNightDisplayTintController, true); - } - private void onDisplayColorModeChanged(int mode) { - if (mode == -1) { + if (mode == NOT_SET) { return; } @@ -732,7 +684,7 @@ public final class ColorDisplayService extends SystemService { mNightDisplayTintController .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode)); - mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature()); + mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting()); updateDisplayWhiteBalanceStatus(); @@ -901,6 +853,71 @@ public final class ColorDisplayService extends SystemService { return availabilityFlags; } + private boolean setNightDisplayAutoModeInternal(@AutoMode int autoMode) { + if (getNightDisplayAutoModeInternal() != autoMode) { + Secure.putStringForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, + null, + mCurrentUser); + } + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mCurrentUser); + } + + private int getNightDisplayAutoModeInternal() { + int autoMode = getNightDisplayAutoModeRawInternal(); + if (autoMode == NOT_SET) { + autoMode = getContext().getResources().getInteger( + R.integer.config_defaultNightDisplayAutoMode); + } + if (autoMode != AUTO_MODE_DISABLED + && autoMode != AUTO_MODE_CUSTOM_TIME + && autoMode != AUTO_MODE_TWILIGHT) { + Slog.e(TAG, "Invalid autoMode: " + autoMode); + autoMode = AUTO_MODE_DISABLED; + } + return autoMode; + } + + private int getNightDisplayAutoModeRawInternal() { + return Secure + .getIntForUser(getContext().getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, + NOT_SET, mCurrentUser); + } + + private Time getNightDisplayCustomStartTimeInternal() { + int startTimeValue = Secure.getIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NOT_SET, mCurrentUser); + if (startTimeValue == NOT_SET) { + startTimeValue = getContext().getResources().getInteger( + R.integer.config_defaultNightDisplayCustomStartTime); + } + return new Time(LocalTime.ofSecondOfDay(startTimeValue / 1000)); + } + + private boolean setNightDisplayCustomStartTimeInternal(Time startTime) { + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, + startTime.getLocalTime().toSecondOfDay() * 1000, + mCurrentUser); + } + + private Time getNightDisplayCustomEndTimeInternal() { + int endTimeValue = Secure.getIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NOT_SET, mCurrentUser); + if (endTimeValue == NOT_SET) { + endTimeValue = getContext().getResources().getInteger( + R.integer.config_defaultNightDisplayCustomEndTime); + } + return new Time(LocalTime.ofSecondOfDay(endTimeValue / 1000)); + } + + private boolean setNightDisplayCustomEndTimeInternal(Time endTime) { + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.getLocalTime().toSecondOfDay() * 1000, + mCurrentUser); + } + /** * Returns the last time the night display transform activation state was changed, or {@link * LocalDateTime#MIN} if night display has never been activated. @@ -930,6 +947,89 @@ public final class ColorDisplayService extends SystemService { .setSaturationLevel(packageName, mCurrentUser, saturationLevel); } + private void setColorModeInternal(@ColorMode int colorMode) { + if (!isColorModeAvailable(colorMode)) { + throw new IllegalArgumentException("Invalid colorMode: " + colorMode); + } + System.putIntForUser(getContext().getContentResolver(), System.DISPLAY_COLOR_MODE, + colorMode, + mCurrentUser); + } + + private @ColorMode + int getColorModeInternal() { + final ContentResolver cr = getContext().getContentResolver(); + if (Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, + 0, mCurrentUser) == 1 + || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 0, mCurrentUser) == 1) { + // There are restrictions on the available color modes combined with a11y transforms. + if (isColorModeAvailable(COLOR_MODE_SATURATED)) { + return COLOR_MODE_SATURATED; + } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { + return COLOR_MODE_AUTOMATIC; + } + } + + int colorMode = System.getIntForUser(cr, System.DISPLAY_COLOR_MODE, -1, mCurrentUser); + if (colorMode == -1) { + // There might be a system property controlling color mode that we need to respect; if + // not, this will set a suitable default. + colorMode = getCurrentColorModeFromSystemProperties(); + } + + // This happens when a color mode is no longer available (e.g., after system update or B&R) + // or the device does not support any color mode. + if (!isColorModeAvailable(colorMode)) { + if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) { + colorMode = COLOR_MODE_NATURAL; + } else if (colorMode == COLOR_MODE_SATURATED + && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) { + colorMode = COLOR_MODE_AUTOMATIC; + } else if (colorMode == COLOR_MODE_AUTOMATIC + && isColorModeAvailable(COLOR_MODE_SATURATED)) { + colorMode = COLOR_MODE_SATURATED; + } else { + colorMode = -1; + } + } + + return colorMode; + } + + /** + * Get the current color mode from system properties, or return -1 if invalid. + * + * See {@link com.android.server.display.DisplayTransformManager} + */ + private @ColorMode + int getCurrentColorModeFromSystemProperties() { + final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0); + if (displayColorSetting == 0) { + return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation")) + ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED; + } else if (displayColorSetting == 1) { + return COLOR_MODE_SATURATED; + } else if (displayColorSetting == 2) { + return COLOR_MODE_AUTOMATIC; + } else { + return -1; + } + } + + private boolean isColorModeAvailable(@ColorMode int colorMode) { + final int[] availableColorModes = getContext().getResources().getIntArray( + R.array.config_availableColorModes); + if (availableColorModes != null) { + for (int mode : availableColorModes) { + if (mode == colorMode) { + return true; + } + } + } + return false; + } + private void dumpInternal(PrintWriter pw) { pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)"); pw.println("Night Display:"); @@ -941,6 +1041,33 @@ public final class ColorDisplayService extends SystemService { mAppSaturationController.dump(pw); } + private boolean isNightDisplayActivatedSetting() { + return Secure.getIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_ACTIVATED, 0, mCurrentUser) == 1; + } + + private int getNightDisplayColorTemperatureSetting() { + return clampNightDisplayColorTemperature(Secure.getIntForUser( + getContext().getContentResolver(), Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, NOT_SET, + mCurrentUser)); + } + + private int clampNightDisplayColorTemperature(int colorTemperature) { + if (colorTemperature == NOT_SET) { + colorTemperature = getContext().getResources().getInteger( + R.integer.config_nightDisplayColorTemperatureDefault); + } + final int minimumTemperature = ColorDisplayManager.getMinimumColorTemperature(getContext()); + final int maximumTemperature = ColorDisplayManager.getMaximumColorTemperature(getContext()); + if (colorTemperature < minimumTemperature) { + colorTemperature = minimumTemperature; + } else if (colorTemperature > maximumTemperature) { + colorTemperature = maximumTemperature; + } + + return colorTemperature; + } + private abstract class NightDisplayAutoMode { public abstract void onActivated(boolean activated); @@ -987,13 +1114,13 @@ public final class ColorDisplayService extends SystemService { // Maintain the existing activated state if within the current period. if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start) && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) { - activate = mNightDisplayController.isActivated(); + activate = isNightDisplayActivatedSetting(); } } if (mNightDisplayTintController.isActivatedStateNotSet() || ( mNightDisplayTintController.isActivated() != activate)) { - mNightDisplayController.setActivated(activate); + mNightDisplayTintController.setActivated(activate); } updateNextAlarm(mNightDisplayTintController.isActivated(), now); @@ -1014,8 +1141,8 @@ public final class ColorDisplayService extends SystemService { intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); getContext().registerReceiver(mTimeChangedReceiver, intentFilter); - mStartTime = mNightDisplayController.getCustomStartTime(); - mEndTime = mNightDisplayController.getCustomEndTime(); + mStartTime = getNightDisplayCustomStartTimeInternal().getLocalTime(); + mEndTime = getNightDisplayCustomEndTimeInternal().getLocalTime(); mLastActivatedTime = getNightDisplayLastActivatedTimeSetting(); @@ -1083,13 +1210,13 @@ public final class ColorDisplayService extends SystemService { // Maintain the existing activated state if within the current period. if (mLastActivatedTime.isBefore(now) && (mLastActivatedTime.isBefore(sunrise) ^ mLastActivatedTime.isBefore(sunset))) { - activate = mNightDisplayController.isActivated(); + activate = isNightDisplayActivatedSetting(); } } if (mNightDisplayTintController.isActivatedStateNotSet() || ( mNightDisplayTintController.isActivated() != activate)) { - mNightDisplayController.setActivated(activate); + mNightDisplayTintController.setActivated(activate); } } @@ -1211,6 +1338,114 @@ public final class ColorDisplayService extends SystemService { public abstract int getLevel(); } + private final class NightDisplayTintController extends TintController { + + private float[] mMatrix = new float[16]; + private final float[] mColorTempCoefficients = new float[9]; + private Integer mColorTemp; + + /** + * Set coefficients based on whether the color matrix is linear or not. + */ + @Override + public void setUp(Context context, boolean needsLinear) { + final String[] coefficients = context.getResources().getStringArray(needsLinear + ? R.array.config_nightDisplayColorTemperatureCoefficients + : R.array.config_nightDisplayColorTemperatureCoefficientsNative); + for (int i = 0; i < 9 && i < coefficients.length; i++) { + mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]); + } + } + + @Override + public void setMatrix(int cct) { + if (mMatrix.length != 16) { + Slog.d(TAG, "The display transformation matrix must be 4x4"); + return; + } + + Matrix.setIdentityM(mMatrix, 0); + + final float squareTemperature = cct * cct; + final float red = squareTemperature * mColorTempCoefficients[0] + + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2]; + final float green = squareTemperature * mColorTempCoefficients[3] + + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5]; + final float blue = squareTemperature * mColorTempCoefficients[6] + + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8]; + mMatrix[0] = red; + mMatrix[5] = green; + mMatrix[10] = blue; + } + + @Override + public float[] getMatrix() { + return isActivated() ? mMatrix : MATRIX_IDENTITY; + } + + @Override + public void setActivated(Boolean activated) { + if (activated == null) { + super.setActivated(null); + return; + } + + boolean activationStateChanged = activated != isActivated(); + + if (!isActivatedStateNotSet() && activationStateChanged) { + // This is a true state change, so set this as the last activation time. + Secure.putStringForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, + LocalDateTime.now().toString(), + mCurrentUser); + } + + if (isActivatedStateNotSet() || activationStateChanged) { + super.setActivated(activated); + Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_ACTIVATED, + activated ? 1 : 0, mCurrentUser); + onActivated(activated); + } + } + + @Override + public int getLevel() { + return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; + } + + void onActivated(boolean activated) { + Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display"); + if (mNightDisplayAutoMode != null) { + mNightDisplayAutoMode.onActivated(activated); + } + + if (ColorDisplayManager.isDisplayWhiteBalanceAvailable(getContext())) { + updateDisplayWhiteBalanceStatus(); + } + + mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_ANIMATED); + } + + int getColorTemperature() { + return mColorTemp != null ? clampNightDisplayColorTemperature(mColorTemp) + : getNightDisplayColorTemperatureSetting(); + } + + boolean setColorTemperature(int temperature) { + mColorTemp = temperature; + final boolean success = Secure.putIntForUser(getContext().getContentResolver(), + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, temperature, mCurrentUser); + onColorTemperatureChanged(temperature); + return success; + } + + void onColorTemperatureChanged(int temperature) { + setMatrix(temperature); + mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE); + } + } + /** * Local service that allows color transforms to be enabled from other system services. */ @@ -1270,7 +1505,7 @@ public final class ColorDisplayService extends SystemService { private final class TintHandler extends Handler { - TintHandler(Looper looper) { + private TintHandler(Looper looper) { super(looper, null, true /* async */); } @@ -1281,6 +1516,12 @@ public final class ColorDisplayService extends SystemService { mGlobalSaturationTintController.setMatrix(msg.arg1); applyTint(mGlobalSaturationTintController, false); break; + case MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE: + applyTint(mNightDisplayTintController, true); + break; + case MSG_APPLY_NIGHT_DISPLAY_ANIMATED: + applyTint(mNightDisplayTintController, false); + break; } } } @@ -1290,11 +1531,37 @@ public final class ColorDisplayService extends SystemService { */ public interface ColorTransformController { - /** Apply the given saturation (grayscale) matrix to the associated AppWindow. */ + /** + * Apply the given saturation (grayscale) matrix to the associated AppWindow. + */ void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation); } - private final class BinderService extends IColorDisplayManager.Stub { + @VisibleForTesting + final class BinderService extends IColorDisplayManager.Stub { + + @Override + public void setColorMode(int colorMode) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set display color mode"); + final long token = Binder.clearCallingIdentity(); + try { + setColorModeInternal(colorMode); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getColorMode() { + final long token = Binder.clearCallingIdentity(); + try { + return getColorModeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } @Override public boolean isDeviceColorManaged() { @@ -1354,8 +1621,139 @@ public final class ColorDisplayService extends SystemService { } @Override + public boolean setNightDisplayActivated(boolean activated) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display activated"); + final long token = Binder.clearCallingIdentity(); + try { + mNightDisplayTintController.setActivated(activated); + return true; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean isNightDisplayActivated() { + final long token = Binder.clearCallingIdentity(); + try { + return mNightDisplayTintController.isActivated(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayColorTemperature(int temperature) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display temperature"); + final long token = Binder.clearCallingIdentity(); + try { + return mNightDisplayTintController.setColorTemperature(temperature); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getNightDisplayColorTemperature() { + final long token = Binder.clearCallingIdentity(); + try { + return mNightDisplayTintController.getColorTemperature(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayAutoMode(int autoMode) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display auto mode"); + final long token = Binder.clearCallingIdentity(); + try { + return setNightDisplayAutoModeInternal(autoMode); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getNightDisplayAutoMode() { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to get night display auto mode"); + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayAutoModeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getNightDisplayAutoModeRaw() { + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayAutoModeRawInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayCustomStartTime(Time startTime) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display custom start time"); + final long token = Binder.clearCallingIdentity(); + try { + return setNightDisplayCustomStartTimeInternal(startTime); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public Time getNightDisplayCustomStartTime() { + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayCustomStartTimeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setNightDisplayCustomEndTime(Time endTime) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set night display custom end time"); + final long token = Binder.clearCallingIdentity(); + try { + return setNightDisplayCustomEndTimeInternal(endTime); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public Time getNightDisplayCustomEndTime() { + final long token = Binder.clearCallingIdentity(); + try { + return getNightDisplayCustomEndTimeInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; + if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { + return; + } final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java index a5e9728e4b68..b1b7d3c8769b 100644 --- a/services/core/java/com/android/server/display/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/DisplayTransformManager.java @@ -17,6 +17,7 @@ package com.android.server.display; import android.app.ActivityTaskManager; +import android.hardware.display.ColorDisplayManager; import android.opengl.Matrix; import android.os.IBinder; import android.os.Parcel; @@ -27,7 +28,6 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.ColorDisplayController; import java.util.Arrays; @@ -248,20 +248,20 @@ public class DisplayTransformManager { * work in linear space. */ public static boolean needsLinearColorMatrix(int colorMode) { - return colorMode != ColorDisplayController.COLOR_MODE_SATURATED; + return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED; } public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) { - if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) { + if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) { applySaturation(COLOR_SATURATION_NATURAL); setDisplayColor(DISPLAY_COLOR_MANAGED); - } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) { + } else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) { applySaturation(COLOR_SATURATION_BOOSTED); setDisplayColor(DISPLAY_COLOR_MANAGED); - } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) { + } else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) { applySaturation(COLOR_SATURATION_NATURAL); setDisplayColor(DISPLAY_COLOR_UNMANAGED); - } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) { + } else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) { applySaturation(COLOR_SATURATION_NATURAL); setDisplayColor(DISPLAY_COLOR_ENHANCED); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3fd3945fc87d..4db541c29448 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -288,6 +288,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final class DebugFlags { static final DebugFlag FLAG_OPTIMIZE_START_INPUT = new DebugFlag("debug.optimize_startinput", false); + static final DebugFlag FLAG_PRE_RENDER_IME_VIEWS = + new DebugFlag("persist.pre_render_ime_views", false); } @UserIdInt @@ -304,6 +306,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final boolean mHasFeature; private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap = new ArrayMap<>(); + private final boolean mIsLowRam; private final HardKeyboardListener mHardKeyboardListener; private final AppOpsManager mAppOpsManager; private final UserManager mUserManager; @@ -403,6 +406,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final ClientDeathRecipient clientDeathRecipient; boolean sessionRequested; + // Determines if IMEs should be pre-rendered. + // DebugFlag can be flipped anytime. This flag is kept per-client to maintain behavior + // through the life of the current client. + boolean shouldPreRenderIme; SessionState curSession; @Override @@ -615,6 +622,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * <dd> * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible. * </dd> + * dt>{@link InputMethodService#IME_INVISIBLE}</dt> + * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is + * currently invisible. + * </dd> * </dl> * <em>Do not update this value outside of setImeWindowStatus.</em> */ @@ -1361,6 +1372,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime); mHardKeyboardBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_externalHardKeyboardBehavior); + mIsLowRam = ActivityManager.isLowRamDeviceStatic(); Bundle extras = new Bundle(); extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true); @@ -1393,7 +1405,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mSettings should be created before buildInputMethodListLocked mSettings = new InputMethodSettings( - mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady); + mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady); updateCurrentProfileIds(); AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); @@ -1682,7 +1694,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, methodList); final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), - mContext.getContentResolver(), methodMap, methodList, userId, true); + mContext.getContentResolver(), methodMap, userId, true); return settings.getEnabledInputMethodListLocked(); } @@ -1738,7 +1750,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return Collections.emptyList(); } final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), - mContext.getContentResolver(), methodMap, methodList, userId, true); + mContext.getContentResolver(), methodMap, userId, true); return settings.getEnabledInputMethodSubtypeListLocked( mContext, imi, allowsImplicitlySelectedSubtypes); } @@ -2264,7 +2276,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mSwitchingDialog != null) return false; if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false; - if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false; + if ((visibility & InputMethodService.IME_ACTIVE) == 0 + || (visibility & InputMethodService.IME_INVISIBLE) != 0) { + return false; + } if (mWindowManagerInternal.isHardKeyboardAvailable()) { if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) { // When physical keyboard is attached, we show the ime switcher (or notification if @@ -2372,6 +2387,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mCurToken == null) { return; } + if (DEBUG) { + Slog.d(TAG, "IME window vis: " + vis + + " active: " + (vis & InputMethodService.IME_ACTIVE) + + " inv: " + (vis & InputMethodService.IME_INVISIBLE)); + } + // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure // all updateSystemUi happens on system previlege. final long ident = Binder.clearCallingIdentity(); @@ -2830,6 +2851,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) { switchUserLocked(userId); } + // Master feature flag that overrides other conditions and forces IME preRendering. + if (DEBUG) { + Slog.v(TAG, "IME PreRendering MASTER flag: " + + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() + + ", LowRam: " + mIsLowRam); + } + // pre-rendering not supported on low-ram devices. + cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam; if (mCurFocusedWindow == windowToken) { if (DEBUG) { @@ -3493,7 +3522,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { setEnabledSessionInMainThread(session); session.method.startInput(startInputToken, inputContext, missingMethods, - editorInfo, restarting); + editorInfo, restarting, session.client.shouldPreRenderIme); } catch (RemoteException e) { } args.recycle(); @@ -4421,6 +4450,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @ShellCommandResult private int refreshDebugProperties() { DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh(); + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh(); return ShellCommandResult.SUCCESS; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 88d1a9c0eb6c..cfc85dac8a53 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -66,9 +66,9 @@ import java.util.Locale; */ final class InputMethodUtils { public static final boolean DEBUG = false; - public static final int NOT_A_SUBTYPE_ID = -1; - public static final String SUBTYPE_MODE_ANY = null; - public static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; + static final int NOT_A_SUBTYPE_ID = -1; + private static final String SUBTYPE_MODE_ANY = null; + static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String TAG = "InputMethodUtils"; private static final Locale ENGLISH_LOCALE = new Locale("en"); private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); @@ -108,7 +108,7 @@ final class InputMethodUtils { // ---------------------------------------------------------------------- // Utilities for debug - public static String getApiCallStack() { + static String getApiCallStack() { String apiCallStack = ""; try { throw new RuntimeException(); @@ -131,10 +131,9 @@ final class InputMethodUtils { } // ---------------------------------------------------------------------- - public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi, - final Context context, final boolean checkDefaultAttribute, - @Nullable final Locale requiredLocale, final boolean checkCountry, - final String requiredSubtypeMode) { + private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context, + boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry, + String requiredSubtypeMode) { if (!imi.isSystem()) { return false; } @@ -148,8 +147,8 @@ final class InputMethodUtils { } @Nullable - public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis, - final Context context) { + private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis, + Context context) { // At first, find the fallback locale from the IMEs that are declared as "default" in the // current locale. Note that IME developers can declare an IME as "default" only for // some particular locales but "not default" for other locales. @@ -177,8 +176,8 @@ final class InputMethodUtils { return null; } - private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi, - final Context context, final boolean checkDefaultAttribute) { + private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi, + Context context, boolean checkDefaultAttribute) { if (!imi.isSystem()) { return false; } @@ -198,7 +197,7 @@ final class InputMethodUtils { return false; } - public static Locale getSystemLocaleFromContext(final Context context) { + private static Locale getSystemLocaleFromContext(Context context) { try { return context.getResources().getConfiguration().locale; } catch (Resources.NotFoundException ex) { @@ -212,10 +211,9 @@ final class InputMethodUtils { @NonNull private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>(); - public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis, - final Context context, final boolean checkDefaultAttribute, - @Nullable final Locale locale, final boolean checkCountry, - final String requiredSubtypeMode) { + InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context, + boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry, + String requiredSubtypeMode) { for (int i = 0; i < imis.size(); ++i) { final InputMethodInfo imi = imis.get(i); if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale, @@ -228,8 +226,7 @@ final class InputMethodUtils { // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be // documented more clearly. - public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis, - final Context context) { + InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) { // If one or more auxiliary input methods are available, OK to stop populating the list. for (final InputMethodInfo imi : mInputMethodSet) { if (imi.isAuxiliaryIme()) { @@ -269,8 +266,8 @@ final class InputMethodUtils { } private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale( - final ArrayList<InputMethodInfo> imis, final Context context, - @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) { + ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale, + @Nullable Locale fallbackLocale) { // Once the system becomes ready, we pick up at least one keyboard in the following order. // Secondary users fall into this category in general. // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true @@ -317,7 +314,7 @@ final class InputMethodUtils { return builder; } - public static ArrayList<InputMethodInfo> getDefaultEnabledImes( + static ArrayList<InputMethodInfo> getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) { final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); // We will primarily rely on the system locale, but also keep relying on the fallback locale @@ -336,13 +333,13 @@ final class InputMethodUtils { return builder.build(); } - public static ArrayList<InputMethodInfo> getDefaultEnabledImes( + static ArrayList<InputMethodInfo> getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis) { return getDefaultEnabledImes(context, imis, false /* onlyMinimum */); } - public static boolean containsSubtypeOf(final InputMethodInfo imi, - @Nullable final Locale locale, final boolean checkCountry, final String mode) { + static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, + boolean checkCountry, String mode) { if (locale == null) { return false; } @@ -371,7 +368,7 @@ final class InputMethodUtils { return false; } - public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { + static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); final int subtypeCount = imi.getSubtypeCount(); for (int i = 0; i < subtypeCount; ++i) { @@ -380,7 +377,7 @@ final class InputMethodUtils { return subtypes; } - public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { + static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { if (enabledImes == null || enabledImes.isEmpty()) { return null; } @@ -404,11 +401,11 @@ final class InputMethodUtils { return enabledImes.get(Math.max(firstFoundSystemIme, 0)); } - public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { + static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID; } - public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { + static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { if (imi != null) { final int subtypeCount = imi.getSubtypeCount(); for (int i = 0; i < subtypeCount; ++i) { @@ -430,7 +427,7 @@ final class InputMethodUtils { }; @VisibleForTesting - public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( + static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( Resources res, InputMethodInfo imi) { final LocaleList systemLocales = res.getConfiguration().getLocales(); @@ -564,7 +561,7 @@ final class InputMethodUtils { * it will return the first subtype matched with mode * @return the most applicable subtypeId */ - public static InputMethodSubtype findLastResortApplicableSubtypeLocked( + static InputMethodSubtype findLastResortApplicableSubtypeLocked( Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, boolean canIgnoreLocaleAsLastResort) { if (subtypes == null || subtypes.size() == 0) { @@ -615,14 +612,13 @@ final class InputMethodUtils { return applicableSubtype; } - public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { + static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { if (subtype == null) return true; return !subtype.isAuxiliary(); } - public static void setNonSelectedSystemImesDisabledUntilUsed( - IPackageManager packageManager, List<InputMethodInfo> enabledImis, - int userId, String callingPackage) { + static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager, + List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) { if (DEBUG) { Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); } @@ -710,7 +706,7 @@ final class InputMethodUtils { } } - public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, + static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, InputMethodSubtype subtype) { final CharSequence imiLabel = imi.loadLabel(context.getPackageManager()); return subtype != null @@ -730,8 +726,8 @@ final class InputMethodUtils { * @param packageName the package name. * @return {@code true} if the package name belongs to the UID. */ - public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager, - final int uid, final String packageName) { + static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager, + @UserIdInt int uid, String packageName) { try { appOpsManager.checkPackage(uid, packageName); return true; @@ -802,10 +798,9 @@ final class InputMethodUtils { return imsList; } - public InputMethodSettings( - Resources res, ContentResolver resolver, - ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, - @UserIdInt int userId, boolean copyOnWrite) { + InputMethodSettings(Resources res, ContentResolver resolver, + ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, + boolean copyOnWrite) { mRes = res; mResolver = resolver; mMethodMap = methodMap; @@ -820,7 +815,7 @@ final class InputMethodUtils { * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual * settings on the {@link Settings.Secure} until we do the first write operation. */ - public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { + void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { if (DEBUG) { Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId); } @@ -834,7 +829,7 @@ final class InputMethodUtils { // TODO: mCurrentProfileIds should be updated here. } - private void putString(@NonNull final String key, @Nullable final String str) { + private void putString(@NonNull String key, @Nullable String str) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, str); } else { @@ -843,7 +838,7 @@ final class InputMethodUtils { } @Nullable - private String getString(@NonNull final String key, @Nullable final String defaultValue) { + private String getString(@NonNull String key, @Nullable String defaultValue) { final String result; if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { result = mCopyOnWriteDataStore.get(key); @@ -853,7 +848,7 @@ final class InputMethodUtils { return result != null ? result : defaultValue; } - private void putInt(final String key, final int value) { + private void putInt(String key, int value) { if (mCopyOnWrite) { mCopyOnWriteDataStore.put(key, String.valueOf(value)); } else { @@ -861,7 +856,7 @@ final class InputMethodUtils { } } - private int getInt(final String key, final int defaultValue) { + private int getInt(String key, int defaultValue) { if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { final String result = mCopyOnWriteDataStore.get(key); return result != null ? Integer.parseInt(result) : 0; @@ -869,11 +864,11 @@ final class InputMethodUtils { return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); } - private void putBoolean(final String key, final boolean value) { + private void putBoolean(String key, boolean value) { putInt(key, value ? 1 : 0); } - private boolean getBoolean(final String key, final boolean defaultValue) { + private boolean getBoolean(String key, boolean defaultValue) { return getInt(key, defaultValue ? 1 : 0) == 1; } @@ -893,12 +888,12 @@ final class InputMethodUtils { } } - public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { + ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { return createEnabledInputMethodListLocked( getEnabledInputMethodsAndSubtypeListLocked()); } - public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( + List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeListLocked(imi); @@ -909,8 +904,7 @@ final class InputMethodUtils { return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); } - public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( - InputMethodInfo imi) { + List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) { List<Pair<String, ArrayList<String>>> imsList = getEnabledInputMethodsAndSubtypeListLocked(); ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); @@ -934,13 +928,13 @@ final class InputMethodUtils { return enabledSubtypes; } - public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { + List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(), mInputMethodSplitter, mSubtypeSplitter); } - public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { + void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { if (reloadInputMethodStr) { getEnabledInputMethodsStr(); } @@ -957,7 +951,7 @@ final class InputMethodUtils { * Build and put a string of EnabledInputMethods with removing specified Id. * @return the specified id was removed or not. */ - public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( + boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { boolean isRemoved = false; boolean needsAppendSeparator = false; @@ -1012,7 +1006,7 @@ final class InputMethodUtils { } @NonNull - public String getEnabledInputMethodsStr() { + String getEnabledInputMethodsStr() { mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, ""); if (DEBUG) { Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache @@ -1080,12 +1074,12 @@ final class InputMethodUtils { } } - public Pair<String, String> getLastInputMethodAndSubtypeLocked() { + Pair<String, String> getLastInputMethodAndSubtypeLocked() { // Gets the first one from the history return getLastSubtypeForInputMethodLockedInternal(null); } - public String getLastSubtypeForInputMethodLocked(String imeId) { + String getLastSubtypeForInputMethodLocked(String imeId) { Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); if (ime != null) { return ime.second; @@ -1203,7 +1197,7 @@ final class InputMethodUtils { return history; } - public void putSelectedInputMethod(String imeId) { + void putSelectedInputMethod(String imeId) { if (DEBUG) { Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " + mCurrentUserId); @@ -1211,7 +1205,7 @@ final class InputMethodUtils { putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId); } - public void putSelectedSubtype(int subtypeId) { + void putSelectedSubtype(int subtypeId) { if (DEBUG) { Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mCurrentUserId); @@ -1220,7 +1214,7 @@ final class InputMethodUtils { } @Nullable - public String getSelectedInputMethod() { + String getSelectedInputMethod() { final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null); if (DEBUG) { Slog.d(TAG, "getSelectedInputMethodStr: " + imi); @@ -1228,7 +1222,7 @@ final class InputMethodUtils { return imi; } - public boolean isSubtypeSelected() { + boolean isSubtypeSelected() { return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; } @@ -1236,11 +1230,11 @@ final class InputMethodUtils { return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID); } - public boolean isShowImeWithHardKeyboardEnabled() { + boolean isShowImeWithHardKeyboardEnabled() { return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false); } - public void setShowImeWithHardKeyboard(boolean show) { + void setShowImeWithHardKeyboard(boolean show) { putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show); } @@ -1249,7 +1243,7 @@ final class InputMethodUtils { return mCurrentUserId; } - public int getSelectedInputMethodSubtypeId(String selectedImiId) { + int getSelectedInputMethodSubtypeId(String selectedImiId) { final InputMethodInfo imi = mMethodMap.get(selectedImiId); if (imi == null) { return NOT_A_SUBTYPE_ID; @@ -1258,8 +1252,8 @@ final class InputMethodUtils { return getSubtypeIdFromHashCode(imi, subtypeHashCode); } - public void saveCurrentInputMethodAndSubtypeToHistory( - String curMethodId, InputMethodSubtype currentSubtype) { + void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, + InputMethodSubtype currentSubtype) { String subtypeId = NOT_A_SUBTYPE_ID_STR; if (currentSubtype != null) { subtypeId = String.valueOf(currentSubtype.hashCode()); @@ -1277,8 +1271,8 @@ final class InputMethodUtils { } } - public static boolean isSoftInputModeStateVisibleAllowed( - int targetSdkVersion, @StartInputFlags int startInputFlags) { + static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion, + @StartInputFlags int startInputFlags) { if (targetSdkVersion < Build.VERSION_CODES.P) { // for compatibility. return true; diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java index 4d9b5f5d01c6..bec1947df228 100644 --- a/services/core/java/com/android/server/job/JobConcurrencyManager.java +++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java @@ -36,6 +36,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.StatLogger; import com.android.server.job.JobSchedulerService.Constants; +import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel; import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.StateController; @@ -148,14 +149,14 @@ class JobConcurrencyManager { Slog.d(TAG, "Interactive: " + interactive); } - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis(); if (interactive) { - mLastScreenOnRealtime = now; + mLastScreenOnRealtime = nowRealtime; mEffectiveInteractiveState = true; mHandler.removeCallbacks(mRampUpForScreenOff); } else { - mLastScreenOffRealtime = now; + mLastScreenOffRealtime = nowRealtime; // Set mEffectiveInteractiveState to false after the delay, when we may increase // the concurrency. @@ -232,38 +233,24 @@ class JobConcurrencyManager { private void updateMaxCountsLocked() { refreshSystemStateLocked(); - if (mEffectiveInteractiveState) { - // Screen on - switch (mLastMemoryTrimLevel) { - case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_MODERATE; - break; - case ProcessStats.ADJ_MEM_FACTOR_LOW: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_LOW; - break; - case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_CRITICAL; - break; - default: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_NORMAL; - break; - } - } else { - // Screen off - switch (mLastMemoryTrimLevel) { - case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_MODERATE; - break; - case ProcessStats.ADJ_MEM_FACTOR_LOW: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_LOW; - break; - case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_CRITICAL; - break; - default: - mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_NORMAL; - break; - } + final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState + ? mConstants.MAX_JOB_COUNTS_SCREEN_ON + : mConstants.MAX_JOB_COUNTS_SCREEN_OFF; + + + switch (mLastMemoryTrimLevel) { + case ProcessStats.ADJ_MEM_FACTOR_MODERATE: + mMaxJobCounts = jobCounts.moderate; + break; + case ProcessStats.ADJ_MEM_FACTOR_LOW: + mMaxJobCounts = jobCounts.low; + break; + case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: + mMaxJobCounts = jobCounts.critical; + break; + default: + mMaxJobCounts = jobCounts.normal; + break; } } @@ -303,7 +290,7 @@ class JobConcurrencyManager { // Initialize the work variables and also count running jobs. mJobCountTracker.reset( - mMaxJobCounts.getTotalMax(), + mMaxJobCounts.getMaxTotal(), mMaxJobCounts.getMaxBg(), mMaxJobCounts.getMinBg()); @@ -482,10 +469,7 @@ class JobConcurrencyManager { } - public void dumpLocked(IndentingPrintWriter pw) { - final long now = System.currentTimeMillis(); - final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis(); - + public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { pw.println("Concurrency:"); pw.increaseIndent(); @@ -522,19 +506,36 @@ class JobConcurrencyManager { } } - public void dumpProtoLocked(ProtoOutputStream proto) { - // TODO Implement it. + public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { + final long token = proto.start(tag); + + proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE, + mCurrentInteractiveState); + proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE, + mEffectiveInteractiveState); + + proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS, + nowRealtime - mLastScreenOnRealtime); + proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS, + nowRealtime - mLastScreenOffRealtime); + + mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER); + + proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, + mLastMemoryTrimLevel); + + proto.end(token); } /** - * This class decides, taking into account {@link #mMaxJobCounts} and how many jos are running / + * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running / * pending, how many more job can start. * * Extracted for testing and logging. */ @VisibleForTesting static class JobCountTracker { - private int mConfigNumTotalMaxJobs; + private int mConfigNumMaxTotalJobs; private int mConfigNumMaxBgJobs; private int mConfigNumMinBgJobs; @@ -552,7 +553,7 @@ class JobConcurrencyManager { private int mNumActualMaxBgJobs; void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) { - mConfigNumTotalMaxJobs = numTotalMaxJobs; + mConfigNumMaxTotalJobs = numTotalMaxJobs; mConfigNumMaxBgJobs = numMaxBgJobs; mConfigNumMinBgJobs = numMinBgJobs; @@ -607,12 +608,12 @@ class JobConcurrencyManager { // However, if there are FG jobs already running, we have to adjust it. mNumReservedForBg = Math.min(reservedForBg, - mConfigNumTotalMaxJobs - mNumRunningFgJobs); + mConfigNumMaxTotalJobs - mNumRunningFgJobs); // Max FG is [total - [number needed for BG jobs]] // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG] final int maxFg = - mConfigNumTotalMaxJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg); + mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg); // The above maxFg is the theoretical max. If there are less FG jobs, the actual // max FG will be lower accordingly. @@ -623,7 +624,7 @@ class JobConcurrencyManager { // Max BG is [total - actual max FG], but cap at [config max BG]. final int maxBg = Math.min( mConfigNumMaxBgJobs, - mConfigNumTotalMaxJobs - mNumActualMaxFgJobs); + mConfigNumMaxTotalJobs - mNumActualMaxFgJobs); // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly. // This isn't needed for the logic to work, but this will give consistent output @@ -669,12 +670,13 @@ class JobConcurrencyManager { final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs; return String.format( "Config={tot=%d bg min/max=%d/%d}" - + " Running: %d / %d (%d)" + + " Running[FG/BG (total)]: %d / %d (%d)" + " Pending: %d / %d (%d)" + " Actual max: %d%s / %d%s (%d%s)" + + " Res BG: %d" + " Starting: %d / %d (%d)" + " Total: %d%s / %d%s (%d%s)", - mConfigNumTotalMaxJobs, + mConfigNumMaxTotalJobs, mConfigNumMinBgJobs, mConfigNumMaxBgJobs, @@ -684,19 +686,37 @@ class JobConcurrencyManager { mNumPendingFgJobs, mNumPendingBgJobs, mNumPendingFgJobs + mNumPendingBgJobs, - mNumActualMaxFgJobs, (totalFg <= mConfigNumTotalMaxJobs) ? "" : "*", + mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*", mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*", mNumActualMaxFgJobs + mNumActualMaxBgJobs, - (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumTotalMaxJobs) + (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs) ? "" : "*", + mNumReservedForBg, + mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs, totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*", totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*", - totalFg + totalBg, (totalFg + totalBg <= mConfigNumTotalMaxJobs) ? "" : "*" + totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*" ); } + + public void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs); + proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs); + proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs); + + proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs); + proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs); + + proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs); + proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs); + + proto.end(token); + } } } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index bd12075fdad3..7625aafd0907 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -366,14 +366,20 @@ public class JobSchedulerService extends com.android.server.SystemService } } - public int getTotalMax() { + /** Total number of jobs to run simultaneously. */ + public int getMaxTotal() { return mTotal.getValue(); } + /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */ public int getMaxBg() { return mMaxBg.getValue(); } + /** + * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any + * pending, rather than always running the TOTAL number of FG jobs. + */ public int getMinBg() { return mMinBg.getValue(); } @@ -384,10 +390,39 @@ public class JobSchedulerService extends com.android.server.SystemService mMinBg.dump(pw, prefix); } - public void dumpProto(ProtoOutputStream proto, long tagTotal, long tagBg) { - mTotal.dumpProto(proto, tagTotal); - mMaxBg.dumpProto(proto, tagBg); - mMinBg.dumpProto(proto, tagBg); + public void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS); + mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG); + mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG); + proto.end(token); + } + } + + /** {@link MaxJobCounts} for each memory trim level. */ + static class MaxJobCountsPerMemoryTrimLevel { + public final MaxJobCounts normal; + public final MaxJobCounts moderate; + public final MaxJobCounts low; + public final MaxJobCounts critical; + + MaxJobCountsPerMemoryTrimLevel( + MaxJobCounts normal, + MaxJobCounts moderate, MaxJobCounts low, + MaxJobCounts critical) { + this.normal = normal; + this.moderate = moderate; + this.low = low; + this.critical = critical; + } + + public void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL); + moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE); + low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW); + critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL); + proto.end(token); } } @@ -546,45 +581,44 @@ public class JobSchedulerService extends com.android.server.SystemService float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR; // Max job counts for screen on / off, for each memory trim level. - final MaxJobCounts MAX_JOB_COUNTS_ON_NORMAL = new MaxJobCounts( - 8, "max_job_total_on_normal", - 6, "max_job_max_bg_on_normal", - 2, "max_job_min_bg_on_normal"); - - final MaxJobCounts MAX_JOB_COUNTS_ON_MODERATE = new MaxJobCounts( - 8, "max_job_total_on_moderate", - 4, "max_job_max_bg_on_moderate", - 2, "max_job_min_bg_on_moderate"); - - final MaxJobCounts MAX_JOB_COUNTS_ON_LOW = new MaxJobCounts( - 5, "max_job_total_on_low", - 1, "max_job_max_bg_on_low", - 1, "max_job_min_bg_on_low"); - - final MaxJobCounts MAX_JOB_COUNTS_ON_CRITICAL = new MaxJobCounts( - 5, "max_job_total_on_critical", - 1, "max_job_max_bg_on_critical", - 1, "max_job_min_bg_on_critical"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_NORMAL = new MaxJobCounts( - 10, "max_job_total_off_normal", - 6, "max_job_max_bg_off_normal", - 2, "max_job_min_bg_off_normal"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_MODERATE = new MaxJobCounts( - 10, "max_job_total_off_moderate", - 4, "max_job_max_bg_off_moderate", - 2, "max_job_min_bg_off_moderate"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_LOW = new MaxJobCounts( - 5, "max_job_total_off_low", - 1, "max_job_max_bg_off_low", - 1, "max_job_min_bg_off_low"); - - final MaxJobCounts MAX_JOB_COUNTS_OFF_CRITICAL = new MaxJobCounts( - 5, "max_job_total_off_critical", - 1, "max_job_max_bg_off_critical", - 1, "max_job_min_bg_off_critical"); + final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON = + new MaxJobCountsPerMemoryTrimLevel( + new MaxJobCounts( + 8, "max_job_total_on_normal", + 6, "max_job_max_bg_on_normal", + 2, "max_job_min_bg_on_normal"), + new MaxJobCounts( + 8, "max_job_total_on_moderate", + 4, "max_job_max_bg_on_moderate", + 2, "max_job_min_bg_on_moderate"), + new MaxJobCounts( + 5, "max_job_total_on_low", + 1, "max_job_max_bg_on_low", + 1, "max_job_min_bg_on_low"), + new MaxJobCounts( + 5, "max_job_total_on_critical", + 1, "max_job_max_bg_on_critical", + 1, "max_job_min_bg_on_critical")); + + final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF = + new MaxJobCountsPerMemoryTrimLevel( + new MaxJobCounts( + 10, "max_job_total_off_normal", + 6, "max_job_max_bg_off_normal", + 2, "max_job_min_bg_off_normal"), + new MaxJobCounts( + 10, "max_job_total_off_moderate", + 4, "max_job_max_bg_off_moderate", + 2, "max_job_min_bg_off_moderate"), + new MaxJobCounts( + 5, "max_job_total_off_low", + 1, "max_job_max_bg_off_low", + 1, "max_job_min_bg_off_low"), + new MaxJobCounts( + 5, "max_job_total_off_critical", + 1, "max_job_max_bg_off_critical", + 1, "max_job_min_bg_off_critical")); + /** Wait for this long after screen off before increasing the job concurrency. */ final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS = @@ -766,15 +800,15 @@ public class JobSchedulerService extends com.android.server.SystemService MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR, DEFAULT_MODERATE_USE_FACTOR); - MAX_JOB_COUNTS_ON_NORMAL.parse(mParser); - MAX_JOB_COUNTS_ON_MODERATE.parse(mParser); - MAX_JOB_COUNTS_ON_LOW.parse(mParser); - MAX_JOB_COUNTS_ON_CRITICAL.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser); + MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser); - MAX_JOB_COUNTS_OFF_NORMAL.parse(mParser); - MAX_JOB_COUNTS_OFF_MODERATE.parse(mParser); - MAX_JOB_COUNTS_OFF_LOW.parse(mParser); - MAX_JOB_COUNTS_OFF_CRITICAL.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser); + MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser); MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT, DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT); @@ -851,15 +885,17 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println(); pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println(); - MAX_JOB_COUNTS_ON_NORMAL.dump(pw, ""); - MAX_JOB_COUNTS_ON_MODERATE.dump(pw, ""); - MAX_JOB_COUNTS_ON_LOW.dump(pw, ""); - MAX_JOB_COUNTS_ON_CRITICAL.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, ""); + + MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, ""); + MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, ""); - MAX_JOB_COUNTS_OFF_NORMAL.dump(pw, ""); - MAX_JOB_COUNTS_OFF_MODERATE.dump(pw, ""); - MAX_JOB_COUNTS_OFF_LOW.dump(pw, ""); - MAX_JOB_COUNTS_OFF_CRITICAL.dump(pw, ""); + SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, ""); pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println(); pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println(); @@ -917,7 +953,11 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR); proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR); - // TODO Dump max job counts. + MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON); + MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF); + + SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto, + ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS); proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT); proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT); @@ -3371,8 +3411,10 @@ public class JobSchedulerService extends com.android.server.SystemService void dumpInternal(final IndentingPrintWriter pw, int filterUid) { final int filterUidFinal = UserHandle.getAppId(filterUid); + final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowUptime = sUptimeMillisClock.millis(); + final Predicate<JobStatus> predicate = (js) -> { return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal; @@ -3548,7 +3590,7 @@ public class JobSchedulerService extends com.android.server.SystemService } pw.println(); - mConcurrencyManager.dumpLocked(pw); + mConcurrencyManager.dumpLocked(pw, now, nowElapsed); pw.println(); pw.print("PersistStats: "); @@ -3560,6 +3602,7 @@ public class JobSchedulerService extends com.android.server.SystemService void dumpInternalProto(final FileDescriptor fd, int filterUid) { ProtoOutputStream proto = new ProtoOutputStream(fd); final int filterUidFinal = UserHandle.getAppId(filterUid); + final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowUptime = sUptimeMillisClock.millis(); final Predicate<JobStatus> predicate = (js) -> { @@ -3703,7 +3746,8 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock); proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive); } - mConcurrencyManager.dumpProtoLocked(proto); + mConcurrencyManager.dumpProtoLocked(proto, + JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed); } proto.flush(); diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java index 591889dde5de..ca9c0e030b4e 100644 --- a/services/core/java/com/android/server/location/GnssVisibilityControl.java +++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java @@ -19,9 +19,15 @@ package com.android.server.location; import android.annotation.SuppressLint; import android.app.AppOpsManager; import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; @@ -55,6 +61,8 @@ class GnssVisibilityControl { private static final String LOCATION_PERMISSION_NAME = "android.permission.ACCESS_FINE_LOCATION"; + private static final String[] NO_LOCATION_ENABLED_PROXY_APPS = new String[0]; + // Wakelocks private static final String WAKELOCK_KEY = TAG; private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000; @@ -66,13 +74,16 @@ class GnssVisibilityControl { private final Handler mHandler; private final Context mContext; + private boolean mIsMasterLocationSettingsEnabled = true; + private boolean mIsOnRoamingNetwork = false; + // Number of non-framework location access proxy apps is expected to be small (< 5). private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7; private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>( HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS); private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener = - uid -> postEvent(() -> handlePermissionsChanged(uid)); + uid -> runOnHandler(() -> handlePermissionsChanged(uid)); GnssVisibilityControl(Context context, Looper looper) { mContext = context; @@ -81,8 +92,15 @@ class GnssVisibilityControl { mHandler = new Handler(looper); mAppOps = mContext.getSystemService(AppOpsManager.class); mPackageManager = mContext.getPackageManager(); + + // Set to empty proxy app list initially until the configuration properties are loaded. + updateNfwLocationAccessProxyAppsInGnssHal(); + + // Listen for proxy app package installation, removal events. + listenForProxyAppsPackageUpdates(); + listenForRoamingNetworkUpdate(); + // TODO(b/122855984): Handle global location settings on/off. - // TODO(b/122856189): Handle roaming case. } void updateProxyApps(List<String> nfwLocationAccessProxyApps) { @@ -90,18 +108,68 @@ class GnssVisibilityControl { // but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling // so that the order of processing is preserved. GnssLocationProvider should // first load the new config parameters for the new SIM and then call this method. - postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps)); + runOnHandler(() -> handleUpdateProxyApps(nfwLocationAccessProxyApps)); + } + + void masterLocationSettingsUpdated(boolean enabled) { + runOnHandler(() -> handleMasterLocationSettingsUpdated(enabled)); } void reportNfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation) { - postEvent(() -> handleNfwNotification( + runOnHandler(() -> handleNfwNotification( new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode, isCachedLocation))); } - private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) { + private void listenForProxyAppsPackageUpdates() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + intentFilter.addDataScheme("package"); + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + + switch (action) { + case Intent.ACTION_PACKAGE_ADDED: + case Intent.ACTION_PACKAGE_REMOVED: + case Intent.ACTION_PACKAGE_REPLACED: + String pkgName = intent.getData().getEncodedSchemeSpecificPart(); + handleProxyAppPackageUpdate(pkgName, action); + break; + } + } + }, UserHandle.ALL, intentFilter, null, mHandler); + } + + private void handleProxyAppPackageUpdate(String pkgName, String action) { + final Boolean locationPermission = mProxyAppToLocationPermissions.get(pkgName); + // pkgName is not one of the proxy apps in our list. + if (locationPermission == null) { + return; + } + + Log.i(TAG, "Proxy app " + pkgName + " package changed: " + action); + final boolean updatedLocationPermission = hasLocationPermission(pkgName); + if (locationPermission != updatedLocationPermission) { + // Permission changed. So, update the GNSS HAL with the updated list. + mProxyAppToLocationPermissions.put(pkgName, updatedLocationPermission); + updateNfwLocationAccessProxyAppsInGnssHal(); + } + } + + private void handleUpdateProxyApps(List<String> nfwLocationAccessProxyApps) { + if (!isProxyAppListUpdated(nfwLocationAccessProxyApps)) { + return; + } + if (nfwLocationAccessProxyApps.isEmpty()) { // Stop listening for app permission changes. Clear the app list in GNSS HAL. if (!mProxyAppToLocationPermissions.isEmpty()) { @@ -125,6 +193,27 @@ class GnssVisibilityControl { updateNfwLocationAccessProxyAppsInGnssHal(); } + private boolean isProxyAppListUpdated(List<String> nfwLocationAccessProxyApps) { + if (nfwLocationAccessProxyApps.size() != mProxyAppToLocationPermissions.size()) { + return true; + } + + for (String nfwLocationAccessProxyApp : nfwLocationAccessProxyApps) { + if (!mProxyAppToLocationPermissions.containsKey(nfwLocationAccessProxyApp)) { + return true; + } + } + + return false; + } + + private void handleMasterLocationSettingsUpdated(boolean enabled) { + mIsMasterLocationSettingsEnabled = enabled; + Log.i(TAG, "Master location settings switch changed to " + + (enabled ? "enabled" : "disabled")); + updateNfwLocationAccessProxyAppsInGnssHal(); + } + // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal private static class NfwNotification { private static final String KEY_PROTOCOL_STACK = "ProtocolStack"; @@ -149,7 +238,7 @@ class GnssVisibilityControl { private final boolean mInEmergencyMode; private final boolean mIsCachedLocation; - NfwNotification(String proxyAppPackageName, byte protocolStack, + private NfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation) { mProxyAppPackageName = proxyAppPackageName; @@ -162,7 +251,7 @@ class GnssVisibilityControl { mIsCachedLocation = isCachedLocation; } - void copyFieldsToIntent(Intent intent) { + private void copyFieldsToIntent(Intent intent) { intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack); if (!TextUtils.isEmpty(mOtherProtocolStackName)) { intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName); @@ -188,7 +277,7 @@ class GnssVisibilityControl { mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation); } - String getResponseTypeAsString() { + private String getResponseTypeAsString() { switch (mResponseType) { case NFW_RESPONSE_TYPE_REJECTED: return "REJECTED"; @@ -246,6 +335,24 @@ class GnssVisibilityControl { } private void updateNfwLocationAccessProxyAppsInGnssHal() { + final String[] locationPermissionEnabledProxyApps = shouldDisableNfwLocationAccess() + ? NO_LOCATION_ENABLED_PROXY_APPS : getLocationPermissionEnabledProxyApps(); + final String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps); + Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: " + + proxyAppsStr); + boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps); + if (!result) { + Log.e(TAG, "Failed to update non-framework location access proxy apps in the" + + " GNSS HAL to: " + proxyAppsStr); + } + } + + private boolean shouldDisableNfwLocationAccess() { + // TODO(b/122856189): Add disableWhenRoaming configuration per proxy app. + return mIsOnRoamingNetwork || !mIsMasterLocationSettingsEnabled; + } + + private String[] getLocationPermissionEnabledProxyApps() { // Get a count of proxy apps with location permission enabled to array creation size. int countLocationPermissionEnabledProxyApps = 0; for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) { @@ -264,15 +371,7 @@ class GnssVisibilityControl { locationPermissionEnabledProxyApps[i++] = proxyApp; } } - - String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps); - Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: " - + proxyAppsStr); - boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps); - if (!result) { - Log.e(TAG, "Failed to update non-framework location access proxy apps in the" - + " GNSS HAL to: " + proxyAppsStr); - } + return locationPermissionEnabledProxyApps; } private void handleNfwNotification(NfwNotification nfwNotification) { @@ -360,7 +459,31 @@ class GnssVisibilityControl { isPermissionMismatched); } - private void postEvent(Runnable event) { + private void listenForRoamingNetworkUpdate() { + // Register for network capabilities changes to monitor roaming changes. + ConnectivityManager mConnMgr = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); + networkRequestBuilder.addCapability(NetworkCapabilities.TRANSPORT_CELLULAR); + NetworkRequest networkRequest = networkRequestBuilder.build(); + mConnMgr.registerNetworkCallback(networkRequest, + new ConnectivityManager.NetworkCallback() { + @Override + public void onCapabilitiesChanged(Network network, + NetworkCapabilities capabilities) { + boolean isRoaming = !capabilities.hasTransport( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + // No locking required for this test and set because the callback + // runs in mHandler thread. + if (mIsOnRoamingNetwork != isRoaming) { + mIsOnRoamingNetwork = isRoaming; + updateNfwLocationAccessProxyAppsInGnssHal(); + } + } + }, mHandler); + } + + private void runOnHandler(Runnable event) { // Hold a wake lock until this message is delivered. // Note that this assumes the message will not be removed from the queue before // it is handled (otherwise the wake lock would be leaked). diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java index 1541b1d520cd..dd26a29d55af 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -42,8 +42,6 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; -import android.media.MediaController2; -import android.media.Session2CommandGroup; import android.media.Session2Token; import android.media.session.ControllerLink; import android.media.session.IActiveSessionsListener; @@ -60,7 +58,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; @@ -1007,17 +1004,50 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { if (DEBUG) { Log.d(TAG, "Session2 is created " + sessionToken); } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } + if (uid != sessionToken.getUid()) { + throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + + " but actually=" + sessionToken.getUid()); + } + int userId = UserHandle.getUserId(uid); + List<Session2Token> session2Tokens = mSession2TokensPerUser.get(userId); + if (session2Tokens.contains(sessionToken)) { + if (DEBUG) { + Log.d(TAG, "notifySession2Created(): Ignoring already existing token " + + sessionToken); + } + return; + } + session2Tokens.add(sessionToken); + pushSession2TokensChangedLocked(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void notifySession2Destroyed(Session2Token sessionToken) throws RemoteException { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + if (DEBUG) { + Log.d(TAG, "Session2 is destroyed " + sessionToken); + } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } if (uid != sessionToken.getUid()) { throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + " but actually=" + sessionToken.getUid()); } - Controller2Callback callback = new Controller2Callback(sessionToken); - // Note: It's safe not to keep controller here because it wouldn't be GC'ed until - // it's closed. - // TODO: Keep controller as well for better readability - // because the GC behavior isn't straightforward. - MediaController2 controller = new MediaController2(mContext, sessionToken, - new HandlerExecutor(mHandler), callback); + int userId = UserHandle.getUserId(uid); + mSession2TokensPerUser.get(userId).remove(sessionToken); + pushSession2TokensChangedLocked(userId); } finally { Binder.restoreCallingIdentity(token); } @@ -2114,30 +2144,4 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget(); } } - - private class Controller2Callback extends MediaController2.ControllerCallback { - private final Session2Token mToken; - - Controller2Callback(Session2Token token) { - mToken = token; - } - - @Override - public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).add(mToken); - pushSession2TokensChangedLocked(userId); - } - } - - @Override - public void onDisconnected(MediaController2 controller) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).remove(mToken); - pushSession2TokensChangedLocked(userId); - } - } - } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 0ab2a7361ac0..eab5c8f866a8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -537,7 +537,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false, - false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR); + false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR, + ""); synchronized (mSessions) { mSessions.put(sessionId, session); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index b8825bbd2d72..494ec3ff67aa 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -155,6 +155,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_IS_FAILED = "isFailed"; private static final String ATTR_IS_APPLIED = "isApplied"; private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode"; + private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; private static final String ATTR_INSTALL_LOCATION = "installLocation"; @@ -267,6 +268,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private boolean mStagedSessionFailed; @GuardedBy("mLock") private int mStagedSessionErrorCode = SessionInfo.NO_ERROR; + @GuardedBy("mLock") + private String mStagedSessionErrorMessage; /** * Path to the validated base APK for this session, which may point at an @@ -413,7 +416,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, - boolean isFailed, boolean isApplied, int stagedSessionErrorCode) { + boolean isFailed, boolean isApplied, int stagedSessionErrorCode, + String stagedSessionErrorMessage) { mCallback = callback; mContext = context; mPm = pm; @@ -447,6 +451,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionFailed = isFailed; mStagedSessionApplied = isApplied; mStagedSessionErrorCode = stagedSessionErrorCode; + mStagedSessionErrorMessage = + stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; if (sealed) { synchronized (mLock) { try { @@ -499,7 +505,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.isSessionApplied = mStagedSessionApplied; info.isSessionReady = mStagedSessionReady; info.isSessionFailed = mStagedSessionFailed; - info.setStagedSessionErrorCode(mStagedSessionErrorCode); + info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage); } return info; } @@ -1971,17 +1977,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionApplied = false; mStagedSessionFailed = false; mStagedSessionErrorCode = SessionInfo.NO_ERROR; + mStagedSessionErrorMessage = ""; } mCallback.onStagedSessionChanged(this); } /** {@hide} */ - void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) { + void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, + String errorMessage) { synchronized (mLock) { mStagedSessionReady = false; mStagedSessionApplied = false; mStagedSessionFailed = true; mStagedSessionErrorCode = errorCode; + mStagedSessionErrorMessage = errorMessage; + Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage); } mCallback.onStagedSessionChanged(this); } @@ -1993,6 +2003,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionApplied = true; mStagedSessionFailed = false; mStagedSessionErrorCode = SessionInfo.NO_ERROR; + mStagedSessionErrorMessage = ""; } mCallback.onStagedSessionChanged(this); } @@ -2017,6 +2028,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return mStagedSessionErrorCode; } + /** {@hide} */ + String getStagedSessionErrorMessage() { + return mStagedSessionErrorMessage; + } + private void destroyInternal() { synchronized (mLock) { mSealed = true; @@ -2133,6 +2149,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed); writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied); writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode); + writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE, + mStagedSessionErrorMessage); // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after // we've read all sessions. writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId); @@ -2253,6 +2271,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED); final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE, SessionInfo.NO_ERROR); + final String stagedSessionErrorMessage = readStringAttribute(in, + ATTR_STAGED_SESSION_ERROR_MESSAGE); if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { throw new IllegalArgumentException("Can't restore staged session with invalid state."); @@ -2296,7 +2316,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { installerThread, stagingManager, sessionId, userId, installerPackageName, installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, - stagedSessionErrorCode); + stagedSessionErrorCode, stagedSessionErrorMessage); } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9100f6aec6d9..6eff8155dffa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -449,8 +449,7 @@ public class PackageManagerService extends IPackageManager.Stub private static final long BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60); - private static final boolean PRECOMPILED_LAYOUT_ENABLED = - SystemProperties.getBoolean("view.precompiled_layout_enabled", false); + private static final String PRECOMPILE_LAYOUTS = "pm.precompile_layouts"; private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; @@ -9119,7 +9118,7 @@ public class PackageManagerService extends IPackageManager.Stub pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT; } - if (PRECOMPILED_LAYOUT_ENABLED) { + if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { mArtManagerService.compileLayouts(pkg); } @@ -16211,7 +16210,7 @@ public class PackageManagerService extends IPackageManager.Stub if (performDexopt) { // Compile the layout resources. - if (PRECOMPILED_LAYOUT_ENABLED) { + if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts"); mViewCompiler.compileLayouts(pkg); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 563fd7f90c4b..84c8b606a9d9 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -690,6 +690,10 @@ class ShortcutPackage extends ShortcutPackageItem { return result; } + public boolean hasShareTargets() { + return !mShareTargets.isEmpty(); + } + /** * Return the filenames (excluding path names) of icon bitmap files from this package. */ diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index fdbaba24966b..792b34c16551 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -2167,6 +2167,19 @@ public class ShortcutService extends IShortcutService.Stub { } } + @Override + public boolean hasShareTargets(String packageName, String packageToCheck, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + enforceSystem(); + + synchronized (mLock) { + throwIfUserLockedL(userId); + + return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets(); + } + } + @GuardedBy("mLock") private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 5311c2a55931..c4d27e5882c4 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -153,6 +153,19 @@ public class StagingManager { return success; } + private static boolean sendMarkStagedSessionReadyRequest(int sessionId) { + final IApexService apex = IApexService.Stub.asInterface( + ServiceManager.getService("apexservice")); + boolean success; + try { + success = apex.markStagedSessionReady(sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } + return success; + } + private static boolean isApexSession(@NonNull PackageInstallerSession session) { return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0; } @@ -166,6 +179,7 @@ public class StagingManager { if (!session.isMultiPackage() && isApexSession(session)) { success = submitSessionToApexService(session, null, apexInfoList); + } else if (session.isMultiPackage()) { List<PackageInstallerSession> childSessions = Arrays.stream(session.getChildSessionIds()) @@ -179,7 +193,13 @@ public class StagingManager { } // else this is a staged multi-package session with no APEX files. } - if (success && (apexInfoList.apexInfos.length > 0)) { + if (!success) { + session.setStagedSessionFailed( + SessionInfo.VERIFICATION_FAILED, + "APEX staging failed, check logcat messages from apexd for more details."); + } + + if (apexInfoList.apexInfos.length > 0) { // For APEXes, we validate the signature here before we mark the session as ready, // so we fail the session early if there is a signature mismatch. For APKs, the // signature verification will be done by the package manager at the point at which @@ -190,16 +210,22 @@ public class StagingManager { for (ApexInfo apexPackage : apexInfoList.apexInfos) { if (!validateApexSignatureLocked(apexPackage.packagePath, apexPackage.packageName)) { - success = false; - break; + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED, + "APK-container signature verification failed for package " + + apexPackage.packageName + ". Signature of file " + + apexPackage.packagePath + " does not match the signature of " + + " the package already installed."); + // TODO(b/118865310): abort the session on apexd. + return; } } } - if (success) { - session.setStagedSessionReady(); - } else { - session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); + session.setStagedSessionReady(); + if (!sendMarkStagedSessionReadyRequest(session.sessionId)) { + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED, + "APEX staging failed, check logcat messages from apexd for more " + + "details."); } } @@ -217,13 +243,20 @@ public class StagingManager { return; } if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) { - session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED); + session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED, + "APEX activation failed. Check logcat messages from apexd for " + + "more information."); + } + if (apexSessionInfo.isVerified) { + // Session has been previously submitted to apexd, but didn't complete all the + // pre-reboot verification, perhaps because the device rebooted in the meantime. + // Greedily re-trigger the pre-reboot verification. + mBgHandler.post(() -> preRebootVerification(session)); } if (apexSessionInfo.isActivated) { session.setStagedSessionApplied(); // TODO(b/118865310) if multi-package proceed with the installation of APKs. } - // TODO(b/118865310) if (apexSessionInfo.isVerified) { /* mark this as staged in apexd */ } // In every other case apexd will retry to apply the session at next boot. } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index aaa187468f8d..2455113d8874 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -16,6 +16,10 @@ package com.android.server.pm; +import com.google.android.collect.Sets; + +import com.android.internal.util.Preconditions; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -38,10 +42,6 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.util.Preconditions; - -import com.google.android.collect.Sets; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 0e40a00077b5..13c4d886e7b1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2569,6 +2569,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = { + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, }; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index e1a911e8ada5..1d829707f180 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -825,16 +825,16 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { // like the ANR / app crashed dialogs return canAddInternalSystemWindow ? 11 : 10; case TYPE_APPLICATION_OVERLAY: - return 12; + return canAddInternalSystemWindow ? 13 : 12; case TYPE_DREAM: // used for Dreams (screensavers with TYPE_DREAM windows) - return 13; + return 14; case TYPE_INPUT_METHOD: // on-screen keyboards and other such input method user interfaces go here. - return 14; + return 15; case TYPE_INPUT_METHOD_DIALOG: // on-screen keyboards and other such input method user interfaces go here. - return 15; + return 16; case TYPE_STATUS_BAR: return 17; case TYPE_STATUS_BAR_PANEL: diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java index 055c941f8b0a..7f2dedb70514 100644 --- a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java +++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java @@ -18,6 +18,7 @@ package com.android.server.policy.role; import android.annotation.NonNull; import android.app.role.RoleManager; +import android.content.ComponentName; import android.content.Context; import android.os.Debug; import android.provider.Settings; @@ -90,6 +91,17 @@ public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHolder return CollectionUtils.singletonOrEmpty(result); } + case RoleManager.ROLE_ASSISTANT: { + String legacyAssistant = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.ASSISTANT, userId); + + if (legacyAssistant == null || legacyAssistant.isEmpty()) { + return Collections.emptyList(); + } else { + return Collections.singletonList( + ComponentName.unflattenFromString(legacyAssistant).getPackageName()); + } + } default: { Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName); return Collections.emptyList(); diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index c0517fdc99d0..1c7596b80fd7 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -198,6 +198,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders // for a given role before adding a migration statement for it here migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId); + migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId); // Some vital packages state has changed since last role grant // Run grants again diff --git a/services/core/java/com/android/server/rollback/LocalIntentReceiver.java b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java new file mode 100644 index 000000000000..504a3496147c --- /dev/null +++ b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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.rollback; + +import android.content.IIntentReceiver; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.IBinder; + +import java.util.function.Consumer; + +/** {@code IntentSender} implementation for RollbackManager internal use. */ +class LocalIntentReceiver { + final Consumer<Intent> mConsumer; + + LocalIntentReceiver(Consumer<Intent> consumer) { + mConsumer = consumer; + } + + private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + mConsumer.accept(intent); + } + }; + + public IntentSender getIntentSender() { + return new IntentSender((IIntentSender) mLocalSender); + } +} diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 693d5d67c7ba..8b4c410000bb 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -19,8 +19,6 @@ package com.android.server.rollback; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.IIntentReceiver; -import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; @@ -36,11 +34,9 @@ import android.content.rollback.IRollbackManager; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.os.Binder; -import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; -import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.storage.StorageManager; @@ -66,7 +62,6 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; -import java.util.function.Consumer; /** * Implementation of service that manages APK level rollbacks. @@ -116,6 +111,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private final Context mContext; private final HandlerThread mHandlerThread; private final Installer mInstaller; + private final RollbackPackageHealthObserver mPackageHealthObserver; RollbackManagerServiceImpl(Context context) { mContext = context; @@ -128,6 +124,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); + mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); + // Kick off loading of the rollback data from strorage in a background // thread. // TODO: Consider loading the rollback data directly here instead, to @@ -376,28 +374,32 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { final LocalIntentReceiver receiver = new LocalIntentReceiver( (Intent result) -> { - // We've now completed the rollback, so we mark it as no longer in - // progress. - data.inProgress = false; - - int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status != PackageInstaller.STATUS_SUCCESS) { - sendFailure(statusReceiver, "Rollback downgrade install failed: " - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)); - return; - } - - addRecentlyExecutedRollback(rollback); - sendSuccess(statusReceiver); - - Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED); - - // TODO: This call emits the warning "Calling a method in the - // system process without a qualified user". Fix that. - // TODO: Limit this to receivers holding the - // MANAGE_ROLLBACKS permission? - mContext.sendBroadcast(broadcast); + getHandler().post(() -> { + // We've now completed the rollback, so we mark it as no longer in + // progress. + data.inProgress = false; + + int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status != PackageInstaller.STATUS_SUCCESS) { + sendFailure(statusReceiver, + "Rollback downgrade install failed: " + + result.getStringExtra( + PackageInstaller.EXTRA_STATUS_MESSAGE)); + return; + } + + addRecentlyExecutedRollback(rollback); + sendSuccess(statusReceiver); + + Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED); + + // TODO: This call emits the warning "Calling a method in the + // system process without a qualified user". Fix that. + // TODO: Limit this to receivers holding the + // MANAGE_ROLLBACKS permission? + mContext.sendBroadcast(broadcast); + }); } ); @@ -820,26 +822,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { }); } - private class LocalIntentReceiver { - final Consumer<Intent> mConsumer; - - LocalIntentReceiver(Consumer<Intent> consumer) { - mConsumer = consumer; - } - - private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { - @Override - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, - IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - getHandler().post(() -> mConsumer.accept(intent)); - } - }; - - public IntentSender getIntentSender() { - return new IntentSender((IIntentSender) mLocalSender); - } - } - /** * Gets the version of the package currently installed. * Returns null if the package is not currently installed. @@ -906,7 +888,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { ensureRollbackDataLoadedLocked(); mAvailableRollbacks.add(data); } - + // TODO(zezeozue): Provide API to explicitly start observing instead + // of doing this for all rollbacks. If we do this for all rollbacks, + // should document in PackageInstaller.SessionParams#setEnableRollback + // After enabling and commiting any rollback, observe packages and + // prepare to rollback if packages crashes too frequently. + List<String> packages = new ArrayList<>(); + for (int i = 0; i < data.packages.size(); i++) { + packages.add(data.packages.get(i).getPackageName()); + } + mPackageHealthObserver.startObservingHealth(packages, + ROLLBACK_LIFETIME_DURATION_MILLIS); scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java new file mode 100644 index 000000000000..1f2f1ccd7383 --- /dev/null +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.rollback; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInstaller; +import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; +import android.os.Handler; +import android.os.HandlerThread; + +import com.android.server.PackageWatchdog; +import com.android.server.PackageWatchdog.PackageHealthObserver; + +import java.util.List; + +/** + * {@code PackageHealthObserver} for {@code RollbackManagerService}. + * + * @hide + */ +public final class RollbackPackageHealthObserver implements PackageHealthObserver { + private static final String TAG = "RollbackPackageHealthObserver"; + private static final String NAME = "rollback-observer"; + private Context mContext; + private Handler mHandler; + + RollbackPackageHealthObserver(Context context) { + mContext = context; + HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver"); + handlerThread.start(); + mHandler = handlerThread.getThreadHandler(); + PackageWatchdog.getInstance(mContext).registerHealthObserver(this); + } + + @Override + public boolean onHealthCheckFailed(String packageName) { + RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); + RollbackInfo rollback = rollbackManager.getAvailableRollback(packageName); + if (rollback != null) { + // TODO(zezeozue): Only rollback if rollback version == failed package version + mHandler.post(() -> executeRollback(rollbackManager, rollback)); + return true; + } + // Don't handle the notification, no rollbacks available + return false; + } + + /** + * Start observing health of {@code packages} for {@code durationMs}. + * This may cause {@code packages} to be rolled back if they crash too freqeuntly. + */ + public void startObservingHealth(List<String> packages, long durationMs) { + PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs); + } + + private void executeRollback(RollbackManager manager, RollbackInfo rollback) { + // TODO(zezeozue): Log initiated metrics + LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> { + mHandler.post(() -> { + int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status == PackageInstaller.STATUS_SUCCESS) { + // TODO(zezeozue); Log success metrics + // Rolledback successfully, no action required by other observers + } else { + // TODO(zezeozue); Log failure metrics + // Rollback failed other observers should have a shot + } + }); + }); + manager.executeRollback(rollback, rollbackReceiver.getIntentSender()); + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 891c3da90b93..3a754c411684 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2286,7 +2286,7 @@ class ActivityStack extends ConfigurationContainer { * Check if the display to which this stack is attached has * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied. */ - private boolean canShowWithInsecureKeyguard() { + boolean canShowWithInsecureKeyguard() { final ActivityDisplay activityDisplay = getDisplay(); if (activityDisplay == null) { throw new IllegalStateException("Stack is not attached to any display, stackId=" diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d36e545aa74f..38580bc41f74 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -747,12 +747,23 @@ class ActivityStarter { abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); - // not sure if we need to create START_ABORTED_BACKGROUND so for now piggybacking - // on START_ABORTED + boolean abortBackgroundStart = false; if (!abort) { - abort |= shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, - realCallingUid, callerApp, originatingPendingIntent, + abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid, + callingPackage, realCallingUid, callerApp, originatingPendingIntent, allowBackgroundActivityStart, intent); + abort |= (abortBackgroundStart && !mService.isBackgroundActivityStartsEnabled()); + // TODO: remove this toast after feature development is done + if (abortBackgroundStart) { + final String toastMsg = abort + ? "Background activity start from " + callingPackage + + " blocked. See go/q-bg-block." + : "This background activity start from " + callingPackage + + " will be blocked in future Q builds. See go/q-bg-block."; + mService.mUiHandler.post(() -> { + Toast.makeText(mService.mContext, toastMsg, Toast.LENGTH_LONG).show(); + }); + } } // Merge the two options bundles, while realCallerOptions takes precedence. @@ -798,8 +809,6 @@ class ActivityStarter { // We pretend to the caller that it was really started, but // they will just get a cancel result. ActivityOptions.abort(checkedOptions); - maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, - null /*r*/, originatingPendingIntent, true /*abortedStart*/); return START_ABORTED; } @@ -818,6 +827,8 @@ class ActivityStarter { final int flags = intent.getFlags(); Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); newIntent.setFlags(flags + | FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); @@ -892,8 +903,11 @@ class ActivityStarter { mService.onStartActivitySetDidAppSwitch(); mController.doPendingActivityLaunches(false); - maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r, - originatingPendingIntent, false /*abortedStart*/); + // maybe log to TRON, but only if we haven't already in shouldAbortBackgroundActivityStart() + if (!abortBackgroundStart) { + maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r, + originatingPendingIntent, false /*abortedStart*/); + } return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask, outActivity); @@ -903,11 +917,9 @@ class ActivityStarter { final String callingPackage, int realCallingUid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart, Intent intent) { - if (mService.isBackgroundActivityStartsEnabled()) { - return false; - } // don't abort for the most important UIDs - if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) { + if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID + || callingUid == Process.NFC_UID) { return false; } // don't abort if the callerApp has any visible activity @@ -915,14 +927,15 @@ class ActivityStarter { return false; } // don't abort if the callingUid is in the foreground or is a persistent system process - final boolean isCallingUidForeground = isUidForeground(callingUid); + final boolean isCallingUidForeground = mService.isUidForeground(callingUid); final boolean isCallingUidPersistentSystemProcess = isUidPersistentSystemProcess( callingUid); if (isCallingUidForeground || isCallingUidPersistentSystemProcess) { return false; } // take realCallingUid into consideration - final boolean isRealCallingUidForeground = isUidForeground(realCallingUid); + final boolean isRealCallingUidForeground = mService.isUidForeground( + realCallingUid); final boolean isRealCallingUidPersistentSystemProcess = isUidPersistentSystemProcess( realCallingUid); if (realCallingUid != callingUid) { @@ -949,8 +962,8 @@ class ActivityStarter { if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { return false; } - // anything that has fallen through will currently be aborted - Slog.w(TAG, "Blocking background activity start [callingPackage: " + callingPackage + // anything that has fallen through would currently be aborted + Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage + "; callingUid: " + callingUid + "; isCallingUidForeground: " + isCallingUidForeground + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess @@ -962,21 +975,11 @@ class ActivityStarter { + "; isBgStartWhitelisted: " + allowBackgroundActivityStart + "; intent: " + intent + "]"); - // TODO: remove this toast after feature development is done - mService.mUiHandler.post(() -> { - Toast.makeText(mService.mContext, - "Blocking background activity start for " + callingPackage, - Toast.LENGTH_SHORT).show(); - }); + maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, + null /*r*/, originatingPendingIntent, true /*abortedStart*/); return true; } - /** Returns true if uid has a visible window or its process is in a top state. */ - private boolean isUidForeground(int uid) { - return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP) - || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid); - } - /** Returns true if uid is in a persistent state. */ private boolean isUidPersistentSystemProcess(int uid) { return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 67b00b2cfbf1..1a5e6a14e733 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -489,4 +489,7 @@ public abstract class ActivityTaskManagerInternal { */ public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution); + + /** Returns true if uid has a visible window or its process is in a top state. */ + public abstract boolean isUidForeground(int uid); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ea6f4cc4ddbc..5fabde45db55 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4399,6 +4399,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public void registerRemoteAnimationsForDisplay(int displayId, + RemoteAnimationDefinition definition) { + mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "registerRemoteAnimations"); + definition.setCallingPid(Binder.getCallingPid()); + synchronized (mGlobalLock) { + final ActivityDisplay display = mRootActivityContainer.getActivityDisplay(displayId); + if (display == null) { + Slog.e(TAG, "Couldn't find display with id: " + displayId); + return; + } + final long origId = Binder.clearCallingIdentity(); + try { + display.mDisplayContent.registerRemoteAnimations(definition); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ @Override public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) { @@ -5632,6 +5653,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mActiveUids.get(uid, PROCESS_STATE_NONEXISTENT); } + boolean isUidForeground(int uid) { + return (getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP) + || mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid); + } + /** * @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on * the whitelist @@ -7041,5 +7067,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution); } } + + @Override + public boolean isUidForeground(int uid) { + synchronized (mGlobalLock) { + return ActivityTaskManagerService.this.isUidForeground(uid); + } + } } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f3a363a30cf8..6dc73bbb80cb 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -29,6 +29,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPE import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; import static android.view.WindowManager.TRANSIT_TASK_OPEN; @@ -1662,6 +1663,15 @@ public class AppTransition implements Dump { "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:" + " anim=" + a + " transit=" + appTransitionToString(transit) + " isEntrance=true" + " Callers=" + Debug.getCallers(3)); + } else if (transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE) { + // In the absence of a specific adapter, we just want to keep everything stationary. + a = new AlphaAnimation(1.f, 1.f); + a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { + Slog.v(TAG, "applyAnimation:" + + " anim=" + a + " transit=" + appTransitionToString(transit) + + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); + } } else { int animAttr = 0; switch (transit) { diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 49308b8f92b4..8f0a7c08fca8 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -76,6 +76,7 @@ public class AppTransitionController { private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final WallpaperController mWallpaperControllerLocked; + private RemoteAnimationDefinition mRemoteAnimationDefinition = null; private final SparseIntArray mTempTransitionReasons = new SparseIntArray(); @@ -85,6 +86,10 @@ public class AppTransitionController { mWallpaperControllerLocked = mDisplayContent.mWallpaperController; } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { + mRemoteAnimationDefinition = definition; + } + /** * Handle application transition for given display. */ @@ -216,6 +221,21 @@ public class AppTransitionController { return mainWindow != null ? mainWindow.mAttrs : null; } + RemoteAnimationAdapter getRemoteAnimationOverride(AppWindowToken animLpToken, int transit, + ArraySet<Integer> activityTypes) { + final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition(); + if (definition != null) { + final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes); + if (adapter != null) { + return adapter; + } + } + if (mRemoteAnimationDefinition == null) { + return null; + } + return mRemoteAnimationDefinition.getAdapter(transit, activityTypes); + } + /** * Overrides the pending transition with the remote animation defined for the transition in the * set of defined remote animations in the app window token. @@ -229,11 +249,8 @@ public class AppTransitionController { if (animLpToken == null) { return; } - final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition(); - if (definition == null) { - return; - } - final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes); + final RemoteAnimationAdapter adapter = + getRemoteAnimationOverride(animLpToken, transit, activityTypes); if (adapter != null) { animLpToken.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote( adapter); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 65b36a092228..a52f1af3b8b1 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -105,6 +105,7 @@ import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.IApplicationToken; import android.view.InputApplicationHandle; +import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -1621,6 +1622,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree t.reparent(getSurfaceControl(), mTransitChangeLeash); onAnimationLeashCreated(t, mTransitChangeLeash); + // Skip creating snapshot if this transition is controlled by a remote animator which + // doesn't need it. + ArraySet<Integer> activityTypes = new ArraySet<>(); + activityTypes.add(getActivityType()); + RemoteAnimationAdapter adapter = + mDisplayContent.mAppTransitionController.getRemoteAnimationOverride( + this, TRANSIT_TASK_CHANGE_WINDOWING_MODE, activityTypes); + if (adapter != null && !adapter.getChangeNeedsSnapshot()) { + return; + } + if (mThumbnail == null && getTask() != null) { final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController; final ArraySet<Task> tasks = new ArraySet<>(); @@ -1639,6 +1651,11 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return mTransitChangeLeash != null || isChangeTransition(mTransit); } + @VisibleForTesting + AppWindowThumbnail getThumbnail() { + return mThumbnail; + } + @Override void checkAppWindowsReadyToShow() { if (allDrawn == mLastAllDrawn) { @@ -2323,11 +2340,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE; } - private int getDefaultChangeTransitionDuration() { - return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION - * mWmService.getTransitionAnimationScaleLocked()); - } - boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { @@ -2349,7 +2361,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree AnimationAdapter thumbnailAdapter = null; getAnimationBounds(mTmpPoint, mTmpRect); - boolean isChanging = isChangeTransition(transit) && mThumbnail != null; + boolean isChanging = isChangeTransition(transit) && enter; // Delaying animation start isn't compatible with remote animations at all. if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null @@ -2361,18 +2373,20 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree adapter = adapters.mAdapter; thumbnailAdapter = adapters.mThumbnailAdapter; } else if (isChanging) { - int duration = getDefaultChangeTransitionDuration(); + final float durationScale = mWmService.getTransitionAnimationScaleLocked(); mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y); adapter = new LocalAnimationAdapter( new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, - getDisplayContent().getDisplayInfo(), duration, + getDisplayContent().getDisplayInfo(), durationScale, true /* isAppAnimation */, false /* isThumbnail */), mWmService.mSurfaceAnimationRunner); - thumbnailAdapter = new LocalAnimationAdapter( - new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, - getDisplayContent().getDisplayInfo(), duration, - true /* isAppAnimation */, true /* isThumbnail */), - mWmService.mSurfaceAnimationRunner); + if (mThumbnail != null) { + thumbnailAdapter = new LocalAnimationAdapter( + new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, + getDisplayContent().getDisplayInfo(), durationScale, + true /* isAppAnimation */, true /* isThumbnail */), + mWmService.mSurfaceAnimationRunner); + } mTransit = transit; mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); } else { diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 650d0be76dc5..ce3fb51aa9f5 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -41,6 +41,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; import java.util.ArrayList; @@ -522,6 +524,11 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { mChangeListeners.remove(listener); } + @VisibleForTesting + boolean containsListener(ConfigurationContainerListener listener) { + return mChangeListeners.contains(listener); + } + /** * Must be called when new parent for the container was set. */ diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8f976e74670d..7a9ff527fc08 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -162,6 +162,7 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InsetsState.InternalInsetType; import android.view.MagnificationSpec; +import android.view.RemoteAnimationDefinition; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -1056,11 +1057,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ void setInsetProvider(@InternalInsetType int type, WindowState win, @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) { - if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) { - if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) { - return; - } - } mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider); } @@ -1091,6 +1087,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mLastWindowForcedOrientation; } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { + mAppTransitionController.registerRemoteAnimations(definition); + } + /** * Temporarily pauses rotation changes until resumed. * diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 6d3c69385a09..40063326e76e 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -58,6 +58,7 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLES import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -2029,7 +2030,8 @@ public class DisplayPolicy { of.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); pf.set(displayFrames.mRestricted); - } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) { + } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT + || type == TYPE_APPLICATION_OVERLAY) { // These dialogs are stable to interim decor changes. cf.set(displayFrames.mStable); of.set(displayFrames.mStable); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index e49e4c0711bd..e79820338c55 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -16,7 +16,13 @@ package com.android.server.wm; +import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.ViewRootImpl.sNewInsetsMode; import android.annotation.NonNull; import android.annotation.Nullable; @@ -59,6 +65,7 @@ class InsetsSourceProvider { */ private boolean mServerVisible; + private final boolean mControllable; InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent) { @@ -66,6 +73,15 @@ class InsetsSourceProvider { mSource = source; mDisplayContent = displayContent; mStateController = stateController; + + final int type = source.getType(); + if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) { + mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL; + } else if (type == TYPE_IME) { + mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME; + } else { + mControllable = false; + } } InsetsSource getSource() { @@ -73,6 +89,13 @@ class InsetsSourceProvider { } /** + * @return Whether the current flag configuration allows to control this source. + */ + boolean isControllable() { + return mControllable; + } + + /** * Updates the window that currently backs this source. * * @param win The window that links to this source. @@ -91,6 +114,9 @@ class InsetsSourceProvider { mSource.setFrame(new Rect()); } else { mWin.setInsetProvider(this); + if (mControllingWin != null) { + updateControlForTarget(mControllingWin, true /* force */); + } } } @@ -113,8 +139,12 @@ class InsetsSourceProvider { && !mWin.mGivenInsetsPending); } - void updateControlForTarget(@Nullable WindowState target) { - if (target == mControllingWin) { + void updateControlForTarget(@Nullable WindowState target, boolean force) { + if (mWin == null) { + mControllingWin = target; + return; + } + if (target == mControllingWin && !force) { return; } if (target == null) { @@ -161,7 +191,7 @@ class InsetsSourceProvider { } boolean isClientVisible() { - return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible; + return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible; } private class ControlAdapter implements AnimationAdapter { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 32dbe96d39f7..bb0cbb1de470 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -163,18 +163,18 @@ class InsetsStateController { } private void onControlChanged(int type, @Nullable WindowState win) { - if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { - return; - } final WindowState previous = mTypeWinControlMap.get(type); if (win == previous) { return; } - final InsetsSourceProvider controller = mControllers.get(type); + final InsetsSourceProvider controller = getSourceProvider(type); if (controller == null) { return; } - controller.updateControlForTarget(win); + if (!controller.isControllable()) { + return; + } + controller.updateControlForTarget(win, false /* force */); if (previous != null) { removeFromControlMaps(previous, type); mPendingControlChanged.add(previous); diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 177f244129f4..b5be2ac5df98 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -91,9 +91,7 @@ class KeyguardController { */ boolean isKeyguardOrAodShowing(int displayId) { return (mKeyguardShowing || mAodShowing) && !mKeyguardGoingAway - && (displayId == DEFAULT_DISPLAY - ? !isDisplayOccluded(DEFAULT_DISPLAY) - : isShowingOnSecondaryDisplay(displayId)); + && !isDisplayOccluded(displayId); } /** @@ -101,10 +99,7 @@ class KeyguardController { * display, false otherwise */ boolean isKeyguardShowing(int displayId) { - return mKeyguardShowing && !mKeyguardGoingAway - && (displayId == DEFAULT_DISPLAY - ? !isDisplayOccluded(DEFAULT_DISPLAY) - : isShowingOnSecondaryDisplay(displayId)); + return mKeyguardShowing && !mKeyguardGoingAway && !isDisplayOccluded(displayId); } /** @@ -152,14 +147,6 @@ class KeyguardController { updateKeyguardSleepToken(); } - private boolean isShowingOnSecondaryDisplay(int displayId) { - if (mSecondaryDisplayIdsShowing == null) return false; - for (int showingId : mSecondaryDisplayIdsShowing) { - if (displayId == showingId) return true; - } - return false; - } - /** * Called when Keyguard is going away. * @@ -473,8 +460,16 @@ class KeyguardController { if (stack.getTopDismissingKeyguardActivity() != null) { mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity(); } + // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display. + if (mDisplayId != DEFAULT_DISPLAY) { + mOccluded |= stack.canShowWithInsecureKeyguard() + && controller.canDismissKeyguard(); + } + } + // TODO(b/123372519): isShowingDream can only works on default display. + if (mDisplayId == DEFAULT_DISPLAY) { + mOccluded |= controller.mWindowManager.isShowingDream(); } - mOccluded |= controller.mWindowManager.isShowingDream(); // TODO(b/113840485): Handle app transition for individual display, and apply occluded // state change to secondary displays. diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index f760b39c5332..5f95691d5307 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -322,8 +322,10 @@ class RemoteAnimationController implements DeathRecipient { mStartBounds = new Rect(startBounds); mTmpRect.set(startBounds); mTmpRect.offsetTo(0, 0); - mThumbnailAdapter = - new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect); + if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { + mThumbnailAdapter = + new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect); + } } else { mStartBounds = null; } diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java index 7dd7c4f5c958..775d5b2fb79a 100644 --- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java @@ -53,13 +53,15 @@ public class WindowChangeAnimationSpec implements AnimationSpec { private Animation mAnimation; private final boolean mIsThumbnail; + static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION; + public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo, - long duration, boolean isAppAnimation, boolean isThumbnail) { + float durationScale, boolean isAppAnimation, boolean isThumbnail) { mStartBounds = new Rect(startBounds); mEndBounds = new Rect(endBounds); mIsAppAnimation = isAppAnimation; mIsThumbnail = isThumbnail; - createBoundsInterpolator(duration, displayInfo); + createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo); } @Override diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 5eff7d8a8553..e6581df233ef 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2817,7 +2817,7 @@ public class WindowManagerService extends IWindowManager.Stub public boolean isShowingDream() { synchronized (mGlobalLock) { - // TODO: fix this when dream can be shown on non-default display. + // TODO(b/123372519): Fix this when dream can be shown on non-default display. return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw(); } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index c38a974ac774..07f26b4c70bb 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -52,6 +52,7 @@ import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.Watchdog; @@ -330,6 +331,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mRequiredAbi; } + /** Returns ID of display overriding the configuration for this process, or + * INVALID_DISPLAY if no display is overriding. */ + @VisibleForTesting + int getDisplayId() { + return mDisplayId; + } + public void setDebugging(boolean debugging) { mDebugging = debugging; } @@ -761,15 +769,15 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio activityDisplay.registerConfigurationChangeListener(this); } - private void unregisterDisplayConfigurationListenerLocked() { + @VisibleForTesting + void unregisterDisplayConfigurationListenerLocked() { if (mDisplayId == INVALID_DISPLAY) { return; } final ActivityDisplay activityDisplay = mAtm.mRootActivityContainer.getActivityDisplay(mDisplayId); if (activityDisplay != null) { - mAtm.mRootActivityContainer.getActivityDisplay( - mDisplayId).unregisterConfigurationChangeListener(this); + activityDisplay.unregisterConfigurationChangeListener(this); } mDisplayId = INVALID_DISPLAY; } @@ -867,6 +875,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio public boolean appNotResponding(String info, Runnable killAppCallback, Runnable serviceTimeoutCallback) { + Runnable targetRunnable = null; synchronized (mAtm.mGlobalLock) { if (mAtm.mController == null) { return false; @@ -877,18 +886,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int res = mAtm.mController.appNotResponding(mName, mPid, info); if (res != 0) { if (res < 0 && mPid != MY_PID) { - killAppCallback.run(); + targetRunnable = killAppCallback; } else { - serviceTimeoutCallback.run(); + targetRunnable = serviceTimeoutCallback; } - return true; } } catch (RemoteException e) { mAtm.mController = null; Watchdog.getInstance().setActivityController(null); + return false; } - return false; } + if (targetRunnable != null) { + targetRunnable.run(); + return true; + } + return false; } public void onTopProcChanged() { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ce5eb8476764..4f120100693a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4367,6 +4367,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void startAnimation(Animation anim) { + + // If we are an inset provider, all our animations are driven by the inset client. + if (mInsetProvider != null && mInsetProvider.isControllable()) { + return; + } + final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo(); anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(), displayInfo.appWidth, displayInfo.appHeight); @@ -4380,6 +4386,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } private void startMoveAnimation(int left, int top) { + + // If we are an inset provider, all our animations are driven by the inset client. + if (mInsetProvider != null && mInsetProvider.isControllable()) { + return; + } + if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this); final Point oldPosition = new Point(); final Point newPosition = new Point(); diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp index c90113f4b44e..26f6d7428fcc 100644 --- a/services/core/jni/com_android_server_lights_LightsService.cpp +++ b/services/core/jni/com_android_server_lights_LightsService.cpp @@ -39,37 +39,7 @@ using Type = ::android::hardware::light::V2_0::Type; template<typename T> using Return = ::android::hardware::Return<T>; -class LightHal { -private: - static sp<ILight> sLight; - static bool sLightInit; - - LightHal() {} - -public: - static void disassociate() { - sLightInit = false; - sLight = nullptr; - } - - static sp<ILight> associate() { - if ((sLight == nullptr && !sLightInit) || - (sLight != nullptr && !sLight->ping().isOk())) { - // will return the hal if it exists the first time. - sLight = ILight::getService(); - sLightInit = true; - - if (sLight == nullptr) { - ALOGE("Unable to get ILight interface."); - } - } - - return sLight; - } -}; - -sp<ILight> LightHal::sLight = nullptr; -bool LightHal::sLightInit = false; +static bool sLightSupported = true; static bool validate(jint light, jint flash, jint brightness) { bool valid = true; @@ -134,7 +104,6 @@ static void processReturn( const LightState &state) { if (!ret.isOk()) { ALOGE("Failed to issue set light command."); - LightHal::disassociate(); return; } @@ -164,13 +133,11 @@ static void setLight_native( jint offMS, jint brightnessMode) { - if (!validate(light, flashMode, brightnessMode)) { + if (!sLightSupported) { return; } - sp<ILight> hal = LightHal::associate(); - - if (hal == nullptr) { + if (!validate(light, flashMode, brightnessMode)) { return; } @@ -180,6 +147,11 @@ static void setLight_native( { android::base::Timer t; + sp<ILight> hal = ILight::getService(); + if (hal == nullptr) { + sLightSupported = false; + return; + } Return<Status> ret = hal->setLight(type, state); processReturn(ret, type, state); if (t.duration() > 50ms) ALOGD("Excessive delay setting light"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index d8225b38487c..2bf6f357bec8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -108,12 +108,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {} @Override - public void addCrossProfileCalendarPackage(ComponentName admin, String packageName) { - } - - @Override - public boolean removeCrossProfileCalendarPackage(ComponentName admin, String packageName) { - return false; + public void setCrossProfileCalendarPackages(ComponentName admin, List<String> packageNames) { } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 54053a896df9..f79f9bc4ef86 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -70,9 +70,11 @@ import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AF import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; +import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; + import static android.provider.Telephony.Carriers.DPC_URI; import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; @@ -81,10 +83,11 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent .PROVISIONING_ENTRY_POINT_ADB; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; -import static com.android.server.devicepolicy.TransferOwnershipMetadataManager - .ADMIN_TYPE_DEVICE_OWNER; -import static com.android.server.devicepolicy.TransferOwnershipMetadataManager - .ADMIN_TYPE_PROFILE_OWNER; + +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; + + import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -237,11 +240,11 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.JournaledFile; import com.android.internal.util.Preconditions; -import com.android.internal.util.StatLogger; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; +import com.android.internal.util.StatLogger; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; @@ -949,8 +952,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "metered_data_disabled_packages"; private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES = "cross-profile-calendar-packages"; - private static final String TAG_PACKAGE = "package"; - + private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL = + "cross-profile-calendar-packages-null"; DeviceAdminInfo info; @@ -1072,7 +1075,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { String endUserSessionMessage = null; // The whitelist of packages that can access cross profile calendar APIs. - final Set<String> mCrossProfileCalendarPackages = new ArraySet<>(); + // This whitelist should be in default an empty list, which indicates that no package + // is whitelisted. + List<String> mCrossProfileCalendarPackages = Collections.emptyList(); ActiveAdmin(DeviceAdminInfo _info, boolean parent) { info = _info; @@ -1343,11 +1348,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.text(endUserSessionMessage); out.endTag(null, TAG_END_USER_SESSION_MESSAGE); } - if (!mCrossProfileCalendarPackages.isEmpty()) { - out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES); - writeAttributeValuesToXml( - out, TAG_PACKAGE, mCrossProfileCalendarPackages); - out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES); + if (mCrossProfileCalendarPackages == null) { + out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL); + out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL); + } else { + writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES, + mCrossProfileCalendarPackages); } } @@ -1542,8 +1548,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Log.w(LOG_TAG, "Missing text when loading end session message"); } } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) { - readAttributeValues( - parser, TAG_PACKAGE, mCrossProfileCalendarPackages); + mCrossProfileCalendarPackages = readPackageList(parser, tag); + } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) { + mCrossProfileCalendarPackages = null; } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -1759,8 +1766,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.print(prefix); pw.println("parentAdmin:"); parentAdmin.dump(prefix + " ", pw); } - pw.print(prefix); pw.print("mCrossProfileCalendarPackages="); - pw.println(mCrossProfileCalendarPackages); + if (mCrossProfileCalendarPackages != null) { + pw.print(prefix); pw.print("mCrossProfileCalendarPackages="); + pw.println(mCrossProfileCalendarPackages); + } } } @@ -6362,7 +6371,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void forceWipeUser(int userId, String wipeReasonForUser) { + private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) { boolean success = false; try { IActivityManager am = mInjector.getIActivityManager(); @@ -6373,7 +6382,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { success = mUserManagerInternal.removeUserEvenWhenDisallowed(userId); if (!success) { Slog.w(LOG_TAG, "Couldn't remove user " + userId); - } else if (isManagedProfile(userId) && !TextUtils.isEmpty(wipeReasonForUser)) { + } else if (isManagedProfile(userId) && !wipeSilently) { sendWipeProfileNotification(wipeReasonForUser); } } catch (RemoteException re) { @@ -6388,6 +6397,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return; } + Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty"); enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId()); final ActiveAdmin admin; @@ -6447,7 +6457,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { internalReason, /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0); } else { - forceWipeUser(userId, wipeReasonForUser); + forceWipeUser(userId, wipeReasonForUser, (flags & WIPE_SILENTLY) != 0); } } finally { mInjector.binderRestoreCallingIdentity(ident); @@ -13988,55 +13998,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void addCrossProfileCalendarPackage(ComponentName who, String packageName) { + public void setCrossProfileCalendarPackages(ComponentName who, List<String> packageNames) { if (!mHasFeature) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); - Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty"); synchronized (getLockObject()) { final ActiveAdmin admin = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - if (admin.mCrossProfileCalendarPackages.add(packageName)) { - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); - } + admin.mCrossProfileCalendarPackages = packageNames; + saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE) + .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALENDAR_PACKAGES) .setAdmin(who) - .setStrings(packageName) + .setStrings(packageNames == null ? null + : packageNames.toArray(new String[packageNames.size()])) .write(); } @Override - public boolean removeCrossProfileCalendarPackage(ComponentName who, String packageName) { - if (!mHasFeature) { - return false; - } - Preconditions.checkNotNull(who, "ComponentName is null"); - Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty"); - - boolean isRemoved = false; - synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - isRemoved = admin.mCrossProfileCalendarPackages.remove(packageName); - if (isRemoved) { - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); - } - } - if (isRemoved) { - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE) - .setAdmin(who) - .setStrings(packageName) - .write(); - } - return isRemoved; - } - - @Override public List<String> getCrossProfileCalendarPackages(ComponentName who) { if (!mHasFeature) { return Collections.emptyList(); @@ -14046,7 +14028,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { final ActiveAdmin admin = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - return new ArrayList<String>(admin.mCrossProfileCalendarPackages); + return admin.mCrossProfileCalendarPackages; } } @@ -14062,6 +14044,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle); if (admin != null) { + if (admin.mCrossProfileCalendarPackages == null) { + return true; + } return admin.mCrossProfileCalendarPackages.contains(packageName); } } @@ -14077,7 +14062,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle); if (admin != null) { - return new ArrayList<String>(admin.mCrossProfileCalendarPackages); + return admin.mCrossProfileCalendarPackages; } } return Collections.emptyList(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 65eaf5541088..586136802619 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1561,6 +1561,12 @@ public final class SystemServer { traceEnd(); } + // Grants default permissions and defines roles + traceBeginAndSlog("StartRoleManagerService"); + mSystemServiceManager.startService(new RoleManagerService( + mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext))); + traceEnd(); + // We need to always start this service, regardless of whether the // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care // of initializing various settings. It will internally modify its behavior @@ -2011,12 +2017,6 @@ public final class SystemServer { } traceEnd(); - // Grants default permissions and defines roles - traceBeginAndSlog("StartRoleManagerService"); - mSystemServiceManager.startService(new RoleManagerService( - mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext))); - traceEnd(); - // No dependency on Webview preparation in system server. But this should // be completed before allowing 3rd party final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation"; diff --git a/services/net/java/android/net/shared/NetworkObserverRegistry.java b/services/net/java/android/net/shared/NetworkObserverRegistry.java new file mode 100644 index 000000000000..36945f5de2c5 --- /dev/null +++ b/services/net/java/android/net/shared/NetworkObserverRegistry.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.shared; + +import static android.Manifest.permission.NETWORK_STACK; + +import android.content.Context; +import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; +import android.net.INetworkManagementEventObserver; +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.RouteInfo; +import android.os.Handler; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.SystemClock; + +/** + * A class for reporting network events to clients. + * + * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to + * all INetworkManagementEventObserver objects that have registered with it. + * + * TODO: Make the notifyXyz methods protected once subclasses (e.g., the NetworkManagementService + * subclass) no longer call them directly. + * + * TODO: change from RemoteCallbackList to direct in-process callbacks. + */ +public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub { + + private final Context mContext; + private final Handler mDaemonHandler; + private static final String TAG = "NetworkObserverRegistry"; + + /** + * Constructs a new instance and registers it with netd. + * This method should only be called once since netd will reject multiple registrations from + * the same process. + */ + public NetworkObserverRegistry(Context context, Handler handler, INetd netd) + throws RemoteException { + mContext = context; + mDaemonHandler = handler; + netd.registerUnsolicitedEventListener(this); + } + + private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = + new RemoteCallbackList<>(); + + /** + * Registers the specified observer and start sending callbacks to it. + * This method may be called on any thread. + */ + public void registerObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + mObservers.register(observer); + } + + /** + * Unregisters the specified observer and stop sending callbacks to it. + * This method may be called on any thread. + */ + public void unregisterObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); + mObservers.unregister(observer); + } + + @FunctionalInterface + private interface NetworkManagementEventCallback { + void sendCallback(INetworkManagementEventObserver o) throws RemoteException; + } + + private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) { + final int length = mObservers.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + try { + eventCallback.sendCallback(mObservers.getBroadcastItem(i)); + } catch (RemoteException | RuntimeException e) { + } + } + } finally { + mObservers.finishBroadcast(); + } + } + + /** + * Notify our observers of a change in the data activity state of the interface + */ + public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + int uid, boolean fromRadio) { + invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( + Integer.toString(type), isActive, tsNanos)); + } + + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) throws RemoteException { + final long timestampNanos; + if (timestamp <= 0) { + timestampNanos = SystemClock.elapsedRealtimeNanos(); + } else { + timestampNanos = timestamp; + } + mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, isActive, + timestampNanos, uid, false)); + } + + /** + * Notify our observers of a limit reached. + */ + @Override + public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); + } + + /** + * Notify our observers of a limit reached. + */ + public void notifyLimitReached(String limitName, String iface) { + invokeForAllObservers(o -> o.limitReached(limitName, iface)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, + long lifetime, String[] servers) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + /** + * Notify our observers of DNS server information received. + */ + public void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { + invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); + } + + /** + * Notify our observers of a new or updated interface address. + */ + public void notifyAddressUpdated(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressUpdated(iface, address)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); + } + + /** + * Notify our observers of a deleted interface address. + */ + public void notifyAddressRemoved(String iface, LinkAddress address) { + invokeForAllObservers(o -> o.addressRemoved(iface, address)); + } + + + @Override + public void onInterfaceAdded(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); + } + + /** + * Notify our observers of an interface addition. + */ + public void notifyInterfaceAdded(String iface) { + invokeForAllObservers(o -> o.interfaceAdded(iface)); + } + + @Override + public void onInterfaceRemoved(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); + } + + /** + * Notify our observers of an interface removal. + */ + public void notifyInterfaceRemoved(String iface) { + invokeForAllObservers(o -> o.interfaceRemoved(iface)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); + } + + /** + * Notify our observers of an interface status change + */ + public void notifyInterfaceStatusChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); + } + + /** + * Notify our observers of an interface link state change + * (typically, an Ethernet cable has been plugged-in or unplugged). + */ + public void notifyInterfaceLinkStateChanged(String iface, boolean up) { + invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up)); + } + + @Override + public void onRouteChanged(boolean updated, + String route, String gateway, String ifName) throws RemoteException { + final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), + ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), + ifName); + mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); + } + + /** + * Notify our observers of a route change. + */ + public void notifyRouteChange(boolean updated, RouteInfo route) { + if (updated) { + invokeForAllObservers(o -> o.routeUpdated(route)); + } else { + invokeForAllObservers(o -> o.routeRemoved(route)); + } + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't do anything here because this is not a method of INetworkManagementEventObserver. + // Only the NMS subclass will implement this. + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java index d91ce39ea92c..de7d77d4b963 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java @@ -158,7 +158,7 @@ public class MagnificationGestureHandlerTest { boolean detectShortcutTrigger) { MagnificationGestureHandler h = new MagnificationGestureHandler( mContext, mMagnificationController, - detectTripleTap, detectShortcutTrigger); + detectTripleTap, detectShortcutTrigger, DISPLAY_0); mHandler = new TestHandler(h.mDetectingState, mClock) { @Override protected String messageToString(Message m) { diff --git a/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java new file mode 100644 index 000000000000..1a231cf56921 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3; +import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4; +import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION; + +import static com.android.server.am.ActivityManagerService.Injector; +import static com.android.server.am.AppCompactor.compactActionIntToString; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import android.os.Handler; +import android.os.HandlerThread; +import android.provider.DeviceConfig; +import android.support.test.uiautomator.UiDevice; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.appop.AppOpsService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link AppCompactor}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:AppCompactorTest + */ +@RunWith(AndroidJUnit4.class) +public final class AppCompactorTest { + + @Mock private AppOpsService mAppOpsService; + private AppCompactor mCompactorUnderTest; + private HandlerThread mHandlerThread; + private Handler mHandler; + private CountDownLatch mCountDown; + + private static void clearDeviceConfig() throws IOException { + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_USE_COMPACTION); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_ACTION_1); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_ACTION_2); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_1); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_2); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_3); + uiDevice.executeShellCommand( + "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_4); + } + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + clearDeviceConfig(); + mHandlerThread = new HandlerThread(""); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + ActivityManagerService ams = new ActivityManagerService(new TestInjector()); + mCompactorUnderTest = new AppCompactor(ams, + new AppCompactor.PropertyChangedCallbackForTest() { + @Override + public void onPropertyChanged() { + if (mCountDown != null) { + mCountDown.countDown(); + } + } + }); + } + + @After + public void tearDown() throws IOException { + mHandlerThread.quit(); + mCountDown = null; + clearDeviceConfig(); + } + + @Test + public void init_setsDefaults() { + mCompactorUnderTest.init(); + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + assertThat(mCompactorUnderTest.mCompactActionSome, is( + compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, is( + compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2))); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + } + + @Test + public void init_withDeviceConfigSetsParameters() { + // When the DeviceConfig already has a flag value stored (note this test will need to + // change if the default value changes from false). + assertThat(mCompactorUnderTest.DEFAULT_USE_COMPACTION, is(false)); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "true", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, + Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, + Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + + // Then calling init will read and set that flag. + mCompactorUnderTest.init(); + assertThat(mCompactorUnderTest.useCompaction(), is(true)); + assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true)); + + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1))); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1)); + } + + @Test + public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException { + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + // When we call init and change some the flag value... + mCompactorUnderTest.init(); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "true", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then that new flag value is updated in the implementation. + assertThat(mCompactorUnderTest.useCompaction(), is(true)); + assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true)); + + // And again, setting the flag the other way. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "false", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.useCompaction(), is(false)); + } + + @Test + public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + assertThat(mCompactorUnderTest.useCompaction(), + is(mCompactorUnderTest.DEFAULT_USE_COMPACTION)); + mCompactorUnderTest.init(); + + // When we push an invalid flag value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_USE_COMPACTION, "foobar", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then we set the default. + assertThat(mCompactorUnderTest.useCompaction(), is(AppCompactor.DEFAULT_USE_COMPACTION)); + } + + @Test + public void compactAction_listensToDeviceConfigChanges() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new values for the compaction action with reasonable values... + + // There are three possible values for compactAction[Some|Full]. + for (int i = 1; i < 4; i++) { + mCountDown = new CountDownLatch(2); + int expectedSome = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1 + i) % 3 + 1; + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false); + int expectedFull = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2 + i) % 3 + 1; + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then the updates are reflected in the flags. + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(expectedSome))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(expectedFull))); + } + } + + @Test + public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new values for the compaction action with bad values ... + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, "foo", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then the default values are reflected in the flag + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2))); + + mCountDown = new CountDownLatch(2); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_1, "", false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_ACTION_2, "", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + assertThat(mCompactorUnderTest.mCompactActionSome, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1))); + assertThat(mCompactorUnderTest.mCompactActionFull, + is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2))); + } + + @Test + public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override new reasonable throttle values after init... + mCountDown = new CountDownLatch(4); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, + Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + + // Then those flags values are reflected in the compactor. + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1)); + } + + @Test + public void compactThrottle_listensToDeviceConfigChangesBadValues() + throws IOException, InterruptedException { + mCompactorUnderTest.init(); + + // When one of the throttles is overridden with a bad value... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_1, "foo", false); + // Then all the throttles have the defaults set. + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + // Repeat for each of the throttle keys. + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_2, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_3, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + clearDeviceConfig(); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE, + KEY_COMPACT_THROTTLE_4, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1)); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2)); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3)); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull, + is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4)); + } + + private class TestInjector extends Injector { + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandler; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java index ac4a5fe90c06..a3f36b720398 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -24,11 +24,15 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.Manifest; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.app.backup.IBackupManagerMonitor; @@ -39,21 +43,21 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.platform.test.annotations.Presubmit; -import android.provider.Settings; -import android.test.mock.MockContentResolver; import android.util.SparseArray; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.test.FakeSettingsProvider; - +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -99,10 +103,6 @@ public class TrampolineTest { @Mock private Context mContextMock; @Mock - private File mSuppressFileMock; - @Mock - private File mSuppressFileParentMock; - @Mock private IBinder mAgentMock; @Mock private ParcelFileDescriptor mParcelFileDescriptorMock; @@ -114,181 +114,232 @@ public class TrampolineTest { private IBackupManagerMonitor mBackupManagerMonitorMock; @Mock private PrintWriter mPrintWriterMock; + @Mock + private UserManager mUserManagerMock; + @Mock + private UserInfo mUserInfoMock; private FileDescriptor mFileDescriptorStub = new FileDescriptor(); private TrampolineTestable mTrampoline; - private MockContentResolver mContentResolver; + private File mTestDir; + private File mSuppressFile; + private File mActivatedFile; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mUserId = NON_USER_SYSTEM; + mUserId = UserHandle.USER_SYSTEM; SparseArray<UserBackupManagerService> serviceUsers = new SparseArray<>(); - serviceUsers.append(UserHandle.SYSTEM.getIdentifier(), mUserBackupManagerService); + serviceUsers.append(UserHandle.USER_SYSTEM, mUserBackupManagerService); serviceUsers.append(NON_USER_SYSTEM, mUserBackupManagerService); when(mBackupManagerServiceMock.getServiceUsers()).thenReturn(serviceUsers); + when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock); + when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock); + TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock; TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM; TrampolineTestable.sCallingUid = Process.SYSTEM_UID; TrampolineTestable.sBackupDisabled = false; + TrampolineTestable.sUserManagerMock = mUserManagerMock; + + mTestDir = InstrumentationRegistry.getContext().getFilesDir(); + mTestDir.mkdirs(); - when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock); + mSuppressFile = new File(mTestDir, "suppress"); + TrampolineTestable.sSuppressFile = mSuppressFile; + + mActivatedFile = new File(mTestDir, "activate-" + NON_USER_SYSTEM); + TrampolineTestable.sActivatedFiles.append(NON_USER_SYSTEM, mActivatedFile); mTrampoline = new TrampolineTestable(mContextMock); + } - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(mContextMock.getContentResolver()).thenReturn(mContentResolver); + @After + public void tearDown() throws Exception { + mSuppressFile.delete(); + mActivatedFile.delete(); } @Test - public void unlockUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void initializeService_successfullyInitializesBackupService() { mTrampoline.initializeService(); - mTrampoline.unlockUser(UserHandle.USER_SYSTEM); - - verify(mBackupManagerServiceMock).startServiceForUser(UserHandle.USER_SYSTEM); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void unlockUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); - mTrampoline.initializeService(); + public void initializeService_globallyDisabled_nonInitialized() { + TrampolineTestable.sBackupDisabled = true; + TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - mTrampoline.unlockUser(NON_USER_SYSTEM); + trampoline.initializeService(); - verify(mBackupManagerServiceMock, never()).startServiceForUser(NON_USER_SYSTEM); + assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void unlockUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); + public void initializeService_doesNotStartServiceForUsers() { mTrampoline.initializeService(); - mTrampoline.unlockUser(NON_USER_SYSTEM); + verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt()); + } - verify(mBackupManagerServiceMock).startServiceForUser(NON_USER_SYSTEM); + @Test + public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() { + assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - mTrampoline.stopUser(UserHandle.USER_SYSTEM); - - verify(mBackupManagerServiceMock).stopServiceForUser(UserHandle.USER_SYSTEM); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - mTrampoline.stopUser(NON_USER_SYSTEM); - - verify(mBackupManagerServiceMock, never()).stopServiceForUser(NON_USER_SYSTEM); + assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); - + public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated() + throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - mTrampoline.stopUser(NON_USER_SYSTEM); - - verify(mBackupManagerServiceMock).stopServiceForUser(NON_USER_SYSTEM); + assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void initializeService_successfullyInitializesBackupService() { + public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated() + throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + // Don't activate non-system user. - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void initializeService_globallyDisabled_nonInitialized() { - TrampolineTestable.sBackupDisabled = true; - TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - - trampoline.initializeService(); + public void + isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated() + throws Exception { + mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } - // Verify that BackupManagerService is not initialized if suppress file exists. @Test - public void initializeService_suppressFileExists_nonInitialized() throws Exception { - TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - trampoline.createBackupSuppressFileForUser(UserHandle.USER_SYSTEM); - + public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() { + mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.SYSTEM_UID; - trampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void initializeService_doesNotStartServiceForUsers() { + public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() { mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.ROOT_UID; - verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt()); - } + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - @Test - public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() { - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void isBackupServiceActive_forNonSysUser_whenSysUserIsDeactivated_returnsFalse() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() { + mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + try { + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } } @Test - public void setBackupServiceActive_callerSystemUid_serviceCreated() { + public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.SYSTEM_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void setBackupServiceActive_callerRootUid_serviceCreated() { + public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.ROOT_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void setBackupServiceActive_callerNonRootNonSystem_securityExceptionThrown() { + public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; try { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); fail(); } catch (SecurityException expected) { } + } - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + @Test + public void setBackupServiceActive_forNonSystemUserAndCallerWithoutBackupPermission_throws() { + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString()); + mTrampoline.initializeService(); + + try { + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } + } + + @Test + public void setBackupServiceActive_forNonSystemUserAndCallerWithoutUserPermission_throws() { + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission( + eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString()); + mTrampoline.initializeService(); + + try { + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } } @Test public void setBackupServiceActive_backupDisabled_ignored() { TrampolineTestable.sBackupDisabled = true; TrampolineTestable trampoline = new TrampolineTestable(mContextMock); + trampoline.initializeService(); trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); @@ -296,14 +347,8 @@ public class TrampolineTest { } @Test - public void setBackupServiceActive_nonUserSystem_ignored() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); - } - - @Test public void setBackupServiceActive_alreadyActive_ignored() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); assertEquals(1, mTrampoline.getCreateServiceCallsCount()); @@ -315,6 +360,7 @@ public class TrampolineTest { @Test public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); @@ -323,6 +369,7 @@ public class TrampolineTest { @Test public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); @@ -349,6 +396,21 @@ public class TrampolineTest { } @Test + public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() { + mTrampoline.initializeService(); + int otherUser = NON_USER_SYSTEM + 1; + File activateFile = new File(mTestDir, "activate-" + otherUser); + TrampolineTestable.sActivatedFiles.append(otherUser, activateFile); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(mTrampoline.isBackupServiceActive(otherUser)); + activateFile.delete(); + } + + @Test public void dataChanged_calledBeforeInitialize_ignored() throws Exception { mTrampoline.dataChanged(PACKAGE_NAME); verifyNoMoreInteractions(mBackupManagerServiceMock); @@ -1123,7 +1185,7 @@ public class TrampolineTest { @Test public void requestBackup_forwardedToCallingUserId() throws Exception { TrampolineTestable.sCallingUserId = mUserId; - when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES, + when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES, mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456); mTrampoline.initializeService(); @@ -1227,50 +1289,33 @@ public class TrampolineTest { static int sCallingUserId = -1; static int sCallingUid = -1; static BackupManagerService sBackupManagerServiceMock = null; + static File sSuppressFile = null; + static SparseArray<File> sActivatedFiles = new SparseArray<>(); + static UserManager sUserManagerMock = null; private int mCreateServiceCallsCount = 0; - private SparseArray<FakeFile> mSuppressFiles = new SparseArray<>(); - - private static class FakeFile extends File { - private boolean mExists; - - FakeFile(String pathname) { - super(pathname); - } - - @Override - public boolean exists() { - return mExists; - } - - @Override - public boolean delete() { - mExists = false; - return true; - } - - @Override - public boolean createNewFile() throws IOException { - mExists = true; - return true; - } - } TrampolineTestable(Context context) { super(context); } @Override + protected UserManager getUserManager() { + return sUserManagerMock; + } + + @Override public boolean isBackupDisabled() { return sBackupDisabled; } @Override - public File getSuppressFileForUser(int userId) { - if (mSuppressFiles.get(userId) == null) { - FakeFile file = new FakeFile(Integer.toString(userId)); - mSuppressFiles.append(userId, file); - } - return mSuppressFiles.get(userId); + protected File getSuppressFileForSystemUser() { + return sSuppressFile; + } + + @Override + protected File getActivatedFileForNonSystemUser(int userId) { + return sActivatedFiles.get(userId); } protected int binderGetCallingUserId() { @@ -1289,11 +1334,6 @@ public class TrampolineTest { } @Override - protected void createBackupSuppressFileForUser(int userId) throws IOException { - getSuppressFileForUser(userId).createNewFile(); - } - - @Override protected void postToHandler(Runnable runnable) { runnable.run(); } 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 0813e6fa0252..535198b3dbfa 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -5239,6 +5239,45 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity()); } + public void testCrossProfileCalendarPackages_initiallyEmpty() { + setAsProfileOwner(admin1); + final Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet()); + } + + public void testCrossProfileCalendarPackages_reopenDpms() { + setAsProfileOwner(admin1); + dpm.setCrossProfileCalendarPackages(admin1, null); + Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1); + assertTrue(packages == null); + initializeDpms(); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertTrue(packages == null); + + dpm.setCrossProfileCalendarPackages(admin1, Collections.emptySet()); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet()); + initializeDpms(); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet()); + + final String dummyPackageName = "test"; + final Set<String> testPackages = new ArraySet<String>(Arrays.asList(dummyPackageName)); + dpm.setCrossProfileCalendarPackages(admin1, testPackages); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, testPackages); + initializeDpms(); + packages = dpm.getCrossProfileCalendarPackages(admin1); + assertCrossProfileCalendarPackagesEqual(packages, testPackages); + } + + private void assertCrossProfileCalendarPackagesEqual(Set<String> expected, Set<String> actual) { + assertTrue(expected != null); + assertTrue(actual != null); + assertTrue(expected.containsAll(actual)); + assertTrue(actual.containsAll(expected)); + } + private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) { final long ident = mServiceContext.binder.clearCallingIdentity(); mServiceContext.binder.callingUid = diff --git a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java index e0ecd3ee24f0..0b01657868a8 100644 --- a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java @@ -25,6 +25,7 @@ import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; import android.content.ContextWrapper; +import android.hardware.display.ColorDisplayManager; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -45,7 +46,9 @@ import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -67,16 +70,23 @@ public class ColorDisplayServiceTest { private MockTwilightManager mTwilightManager; - private ColorDisplayController mColorDisplayController; private ColorDisplayService mColorDisplayService; + private ColorDisplayController mColorDisplayController; + private ColorDisplayService.BinderService mBinderService; + + @BeforeClass + public static void setDtm() { + final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class); + LocalServices.addService(DisplayTransformManager.class, dtm); + } @Before public void setUp() { mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); - mUserId = ActivityManager.getCurrentUser(); - doReturn(mContext).when(mContext).getApplicationContext(); + mUserId = ActivityManager.getCurrentUser(); + final MockContentResolver cr = new MockContentResolver(mContext); cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); doReturn(cr).when(mContext).getContentResolver(); @@ -84,23 +94,19 @@ public class ColorDisplayServiceTest { final AlarmManager am = Mockito.mock(AlarmManager.class); doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE); - final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class); - LocalServices.addService(DisplayTransformManager.class, dtm); - mTwilightManager = new MockTwilightManager(); LocalServices.addService(TwilightManager.class, mTwilightManager); mColorDisplayController = new ColorDisplayController(mContext, mUserId); mColorDisplayService = new ColorDisplayService(mContext); + mBinderService = mColorDisplayService.new BinderService(); } @After public void tearDown() { - LocalServices.removeServiceForTest(DisplayTransformManager.class); LocalServices.removeServiceForTest(TwilightManager.class); mColorDisplayService = null; - mColorDisplayController = null; mTwilightManager = null; @@ -108,6 +114,11 @@ public class ColorDisplayServiceTest { mContext = null; } + @AfterClass + public static void removeDtm() { + LocalServices.removeServiceForTest(DisplayTransformManager.class); + } + @Test public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() { setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */); @@ -907,15 +918,14 @@ public class ColorDisplayServiceTest { } setAccessibilityColorInversion(true); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(true /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED); - } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED); + } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC); } } @@ -926,15 +936,14 @@ public class ColorDisplayServiceTest { } setAccessibilityColorCorrection(true); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(true /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED); - } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED); + } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC); } } @@ -946,15 +955,14 @@ public class ColorDisplayServiceTest { setAccessibilityColorCorrection(true); setAccessibilityColorInversion(true); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(true /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED); - } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) { - assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED); + } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) { + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC); } } @@ -966,12 +974,11 @@ public class ColorDisplayServiceTest { setAccessibilityColorCorrection(false); setAccessibilityColorInversion(false); - setColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); - assertAccessibilityTransformActivated(false /* activated */ ); - assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL); - assertActiveColorMode(ColorDisplayController.COLOR_MODE_NATURAL); + assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); + assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); } /** @@ -981,7 +988,7 @@ public class ColorDisplayServiceTest { * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes) */ private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) { - mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM); + mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME); mColorDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset)); mColorDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset)); } @@ -993,7 +1000,7 @@ public class ColorDisplayServiceTest { * @param sunriseOffset the offset relative to now for sunrise (in minutes) */ private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) { - mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_TWILIGHT); + mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT); mTwilightManager.setTwilightState( getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset)); } @@ -1006,7 +1013,7 @@ public class ColorDisplayServiceTest { * activated (in minutes) */ private void setActivated(boolean activated, int lastActivatedTimeOffset) { - mColorDisplayController.setActivated(activated); + mBinderService.setNightDisplayActivated(activated); Secure.putStringForUser(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(), @@ -1036,10 +1043,10 @@ public class ColorDisplayServiceTest { /** * Configures color mode via ColorDisplayController. * - * @param mode the color mode to set + * @param colorMode the color mode to set */ - private void setColorMode(int mode) { - mColorDisplayController.setColorMode(mode); + private void setColorMode(int colorMode) { + mColorDisplayController.setColorMode(colorMode); } /** @@ -1081,23 +1088,12 @@ public class ColorDisplayServiceTest { * @param activated the expected activated state of Night display */ private void assertActivated(boolean activated) { - assertWithMessage("Invalid Night display activated state") - .that(mColorDisplayController.isActivated()) + assertWithMessage("Incorrect Night display activated state") + .that(mBinderService.isNightDisplayActivated()) .isEqualTo(activated); } /** - * Convenience method for asserting that Accessibility color transform is detected. - * - * @param state {@code true} if any Accessibility transform should be activated - */ - private void assertAccessibilityTransformActivated(boolean state) { - assertWithMessage("Unexpected Accessibility color transform state") - .that(mColorDisplayController.getAccessibilityTransformActivated()) - .isEqualTo(state); - } - - /** * Convenience method for asserting that the active color mode matches expectation. * * @param mode the expected active color mode. diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java index 01199a36ff5b..0219f2201675 100644 --- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java +++ b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java @@ -17,15 +17,15 @@ package com.android.server.job; import android.util.KeyValueListParser; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.server.job.JobSchedulerService.MaxJobCounts; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - @RunWith(AndroidJUnit4.class) @SmallTest public class MaxJobCountsTest { @@ -43,13 +43,14 @@ public class MaxJobCountsTest { counts.parse(parser); - Assert.assertEquals(expectedTotal, counts.getTotalMax()); + Assert.assertEquals(expectedTotal, counts.getMaxTotal()); Assert.assertEquals(expectedMaxBg, counts.getMaxBg()); Assert.assertEquals(expectedMinBg, counts.getMinBg()); } @Test public void test() { + // Tests with various combinations. check("", /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0); check("", /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0); check("", /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0); @@ -58,7 +59,11 @@ public class MaxJobCountsTest { check("", /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5); check("", /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3); check("", /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1); + check("", /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14); + check("", /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15); + check("", /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15); + // Test for overriding with a setting string. check("total=5,maxbg=4,minbg=3", /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3); check("total=5", /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4); check("maxbg=4", /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index 73e96134167a..742ae41ed9f0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -173,7 +173,8 @@ public class PackageInstallerSessionTest { /* isReady */ staged ? true : false, /* isFailed */ false, /* isApplied */false, - /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED); + /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED, + /* stagedSessionErrorMessage */ "some error"); } private void dumpSession(PackageInstallerSession session) { @@ -295,6 +296,8 @@ public class PackageInstallerSessionTest { assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed()); assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady()); assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode()); + assertEquals(expected.getStagedSessionErrorMessage(), + actual.getStagedSessionErrorMessage()); assertEquals(expected.isPrepared(), actual.isPrepared()); assertEquals(expected.isSealed(), actual.isSealed()); assertEquals(expected.isMultiPackage(), actual.isMultiPackage()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index a381023590c3..056568a0de64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -549,8 +549,7 @@ public class ActivityStarterTests extends ActivityTestsBase { verify(mActivityMetricsLogger, times(1)).logActivityStart(any(), any(), any(), eq(FAKE_CALLING_UID), eq(FAKE_CALLING_PACKAGE), anyInt(), anyBoolean(), eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), anyInt(), - eq(ActivityBuilder.getDefaultComponent().getPackageName()), anyInt(), anyBoolean(), - any(), eq(false)); + any(), anyInt(), anyBoolean(), any(), eq(false)); } /** @@ -599,6 +598,10 @@ public class ActivityStarterTests extends ActivityTestsBase { Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1, UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1, false, false, false); + runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false, + Process.NFC_UID, false, PROCESS_STATE_TOP + 1, + UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1, + false, false, false); runAndVerifyBackgroundActivityStartsSubtest( "disallowed_callingUidHasVisibleWindow_notAborted", false, UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java new file mode 100644 index 000000000000..19ace3c0336c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.os.IBinder; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationDefinition; +import android.view.RemoteAnimationTarget; + +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for change transitions + * + * Build/Install/Run: + * atest WmTests:AppChangeTransitionTests + */ +@FlakyTest(detail = "Promote when shown to be stable.") +@SmallTest +public class AppChangeTransitionTests extends WindowTestsBase { + + private TaskStack mStack; + private Task mTask; + private WindowTestUtils.TestAppWindowToken mToken; + + @Before + public void setUp() throws Exception { + mStack = createTaskStackOnDisplay(mDisplayContent); + mTask = createTaskInStack(mStack, 0 /* userId */); + mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + mToken.mSkipOnParentSet = false; + + mTask.addChild(mToken, 0); + } + + class TestRemoteAnimationRunner implements IRemoteAnimationRunner { + @Override + public void onAnimationStart(RemoteAnimationTarget[] apps, + IRemoteAnimationFinishedCallback finishedCallback) { + for (RemoteAnimationTarget target : apps) { + assertNotNull(target.startBounds); + } + try { + finishedCallback.onAnimationFinished(); + } catch (Exception e) { + throw new RuntimeException("Something went wrong"); + } + } + + @Override + public void onAnimationCancelled() { + } + + @Override + public IBinder asBinder() { + return null; + } + } + + @Test + public void testModeChangeRemoteAnimatorNoSnapshot() { + RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); + RemoteAnimationAdapter adapter = + new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false); + definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter); + mDisplayContent.registerRemoteAnimations(definition); + + mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(1, mDisplayContent.mChangingApps.size()); + assertNull(mToken.getThumbnail()); + + waitUntilHandlersIdle(); + mToken.removeImmediately(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 374078625f4a..a498a1a9172a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -94,9 +94,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); topBar.getFrameLw().set(0, 0, 500, 100); mProvider.setWindow(topBar, null); - mProvider.updateControlForTarget(target); + mProvider.updateControlForTarget(target, false /* force */); assertNotNull(mProvider.getControl()); - mProvider.updateControlForTarget(null); + mProvider.updateControlForTarget(null, false /* force */); assertNull(mProvider.getControl()); } @@ -106,7 +106,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); topBar.getFrameLw().set(0, 0, 500, 100); mProvider.setWindow(topBar, null); - mProvider.updateControlForTarget(target); + mProvider.updateControlForTarget(target, false /* force */); InsetsState state = new InsetsState(); state.getSource(TYPE_TOP_BAR).setVisible(false); mProvider.onInsetsModified(target, state.getSource(TYPE_TOP_BAR)); diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 9478be90b5c3..b867799c16cb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -77,7 +77,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { MockitoAnnotations.initMocks(this); when(mMockRunner.asBinder()).thenReturn(new Binder()); - mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50); + mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */); mAdapter.setCallingPid(123); mWm.mH.runWithScissors(() -> mHandler = new TestHandler(null, mClock), 0); mController = new RemoteAnimationController(mWm, mAdapter, mHandler); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java new file mode 100644 index 000000000000..a7c84a1c28b4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.Display.INVALID_DISPLAY; + +import static com.android.server.wm.ActivityDisplay.POSITION_TOP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import android.content.pm.ApplicationInfo; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; + +/** + * Tests for the {@link WindowProcessController} class. + * + * Build/Install/Run: + * atest WmTests:WindowProcessControllerTests + */ +@Presubmit +public class WindowProcessControllerTests extends ActivityTestsBase { + + @Test + public void testDisplayConfigurationListener() { + final WindowProcessController wpc = new WindowProcessController( + mService, mock(ApplicationInfo.class), null, 0, -1, null, null); + //By default, the process should not listen to any display. + assertEquals(INVALID_DISPLAY, wpc.getDisplayId()); + + // Register to display 1 as a listener. + TestActivityDisplay testActivityDisplay1 = createTestActivityDisplayInContainer(); + wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1); + assertTrue(testActivityDisplay1.containsListener(wpc)); + assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId()); + + // Move to display 2. + TestActivityDisplay testActivityDisplay2 = createTestActivityDisplayInContainer(); + wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay2); + assertFalse(testActivityDisplay1.containsListener(wpc)); + assertTrue(testActivityDisplay2.containsListener(wpc)); + assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId()); + + // Null ActivityDisplay will not change anything. + wpc.registerDisplayConfigurationListenerLocked(null); + assertTrue(testActivityDisplay2.containsListener(wpc)); + assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId()); + + // Unregister listener will remove the wpc from registered displays. + wpc.unregisterDisplayConfigurationListenerLocked(); + assertFalse(testActivityDisplay1.containsListener(wpc)); + assertFalse(testActivityDisplay2.containsListener(wpc)); + assertEquals(INVALID_DISPLAY, wpc.getDisplayId()); + + // Unregistration still work even if the display was removed. + wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1); + assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId()); + mRootActivityContainer.removeChild(testActivityDisplay1); + wpc.unregisterDisplayConfigurationListenerLocked(); + assertEquals(INVALID_DISPLAY, wpc.getDisplayId()); + } + + private TestActivityDisplay createTestActivityDisplayInContainer() { + final TestActivityDisplay testActivityDisplay = createNewActivityDisplay(); + mRootActivityContainer.addChild(testActivityDisplay, POSITION_TOP); + return testActivityDisplay; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index 44e998b7e62a..2263cf3a02e0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -152,6 +152,7 @@ public class WindowTestUtils { public static class TestAppWindowToken extends AppWindowToken { boolean mOnTop = false; private Transaction mPendingTransactionOverride; + boolean mSkipOnParentSet = true; private TestAppWindowToken(DisplayContent dc) { super(dc.mWmService, new IApplicationToken.Stub() { @@ -200,7 +201,9 @@ public class WindowTestUtils { @Override void onParentSet() { - // Do nothing. + if (!mSkipOnParentSet) { + super.onParentSet(); + } } @Override diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index d0b7a5e87f99..50e4faab76e3 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -61,6 +61,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.service.usb.UsbPortInfoProto; import android.service.usb.UsbPortManagerProto; +import android.service.usb.UsbServiceProto; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -74,7 +75,6 @@ import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; import java.util.ArrayList; -import java.util.HashMap; import java.util.NoSuchElementException; /** @@ -141,7 +141,11 @@ public class UsbPortManager { // Maintains the current connected status of the port. // Uploads logs only when the connection status is changes. - private final HashMap<String, Boolean> mConnected = new HashMap<>(); + private final ArrayMap<String, Boolean> mConnected = new ArrayMap<>(); + + // Maintains the USB contaminant status that was previously logged. + // Logs get uploaded only when contaminant presence status changes. + private final ArrayMap<String, Integer> mContaminantStatus = new ArrayMap<>(); private NotificationManager mNotificationManager; @@ -959,6 +963,24 @@ public class UsbPortManager { updateContaminantNotification(); } + // Constants have to be converted between USB HAL V1.2 ContaminantDetectionStatus + // to usb.proto as proto guidelines recommends 0 to be UNKNOWN/UNSUPPORTTED + // whereas HAL policy is against a loosely defined constant. + private static int convertContaminantDetectionStatusToProto(int contaminantDetectionStatus) { + switch (contaminantDetectionStatus) { + case UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED: + return UsbServiceProto.CONTAMINANT_STATUS_NOT_SUPPORTED; + case UsbPortStatus.CONTAMINANT_DETECTION_DISABLED: + return UsbServiceProto.CONTAMINANT_STATUS_DISABLED; + case UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED: + return UsbServiceProto.CONTAMINANT_STATUS_NOT_DETECTED; + case UsbPortStatus.CONTAMINANT_DETECTION_DETECTED: + return UsbServiceProto.CONTAMINANT_STATUS_DETECTED; + default: + return UsbServiceProto.CONTAMINANT_STATUS_UNKNOWN; + } + } + private void sendPortChangedBroadcastLocked(PortInfo portInfo) { final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED); intent.addFlags( @@ -973,6 +995,33 @@ public class UsbPortManager { Manifest.permission.MANAGE_USB)); // Log to statsd + + // Port is removed + if (portInfo.mUsbPortStatus == null) { + if (mConnected.containsKey(portInfo.mUsbPort.getId())) { + //Previous logged a connected. Set it to disconnected. + if (mConnected.get(portInfo.mUsbPort.getId())) { + StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED, + StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, + portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis); + } + mConnected.remove(portInfo.mUsbPort.getId()); + } + + if (mContaminantStatus.containsKey(portInfo.mUsbPort.getId())) { + //Previous logged a contaminant detected. Set it to not detected. + if ((mContaminantStatus.get(portInfo.mUsbPort.getId()) + == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED)) { + StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED, + portInfo.mUsbPort.getId(), + convertContaminantDetectionStatusToProto( + UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED)); + } + mContaminantStatus.remove(portInfo.mUsbPort.getId()); + } + return; + } + if (!mConnected.containsKey(portInfo.mUsbPort.getId()) || (mConnected.get(portInfo.mUsbPort.getId()) != portInfo.mUsbPortStatus.isConnected())) { @@ -983,6 +1032,17 @@ public class UsbPortManager { StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis); } + + if (!mContaminantStatus.containsKey(portInfo.mUsbPort.getId()) + || (mContaminantStatus.get(portInfo.mUsbPort.getId()) + != portInfo.mUsbPortStatus.getContaminantDetectionStatus())) { + mContaminantStatus.put(portInfo.mUsbPort.getId(), + portInfo.mUsbPortStatus.getContaminantDetectionStatus()); + StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED, + portInfo.mUsbPort.getId(), + convertContaminantDetectionStatusToProto( + portInfo.mUsbPortStatus.getContaminantDetectionStatus())); + } } private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 8c82cc835ed9..697469a3c680 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -939,11 +939,7 @@ public class SoundTriggerService extends SystemService { runOrAddOperation(new Operation( // always execute: () -> { - // Don't remove the callback if multiple triggers are allowed or - // if this event was triggered by a getModelState request - if (!mRecognitionConfig.allowMultipleTriggers - && event.status - != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { + if (!mRecognitionConfig.allowMultipleTriggers) { // Unregister this remoteService once op is done synchronized (mCallbacksLock) { mCallbacks.remove(mPuuid.getUuid()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index bb01f041c5af..718f2d379883 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -17,13 +17,18 @@ package com.android.server.voiceinteraction; import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; +import android.app.role.OnRoleHoldersChangedListener; +import android.app.role.RoleManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; @@ -78,6 +83,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.concurrent.Executor; /** * SystemService that publishes an IVoiceInteractionManagerService. @@ -200,6 +206,7 @@ public class VoiceInteractionManagerService extends SystemService { VoiceInteractionManagerServiceStub() { mEnableService = shouldEnableService(mContext); + new RoleObserver(mContext.getMainExecutor()); } // TODO: VI Make sure the caller is the current user or profile @@ -1268,6 +1275,106 @@ public class VoiceInteractionManagerService extends SystemService { getActiveServiceComponentName()); } + class RoleObserver implements OnRoleHoldersChangedListener { + private PackageManager mPm = mContext.getPackageManager(); + private RoleManager mRm = mContext.getSystemService(RoleManager.class); + + RoleObserver(@NonNull @CallbackExecutor Executor executor) { + mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL); + } + + private @NonNull String getDefaultRecognizer(@NonNull UserHandle user) { + ResolveInfo resolveInfo = mPm.resolveServiceAsUser( + new Intent(RecognitionService.SERVICE_INTERFACE), + PackageManager.GET_META_DATA, user.getIdentifier()); + + if (resolveInfo == null || resolveInfo.serviceInfo == null) { + Log.w(TAG, "Unable to resolve default voice recognition service."); + return ""; + } + + return new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name).flattenToShortString(); + } + + /** + * Convert the assistant-role holder into settings. The rest of the system uses the + * settings. + * + * @param roleName the name of the role whose holders are changed + * @param user the user for this role holder change + */ + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { + if (!roleName.equals(RoleManager.ROLE_ASSISTANT)) { + return; + } + + List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user); + + if (roleHolders.isEmpty()) { + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ASSISTANT, ""); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_INTERACTION_SERVICE, ""); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer(user)); + } else { + // Assistant is singleton role + String pkg = roleHolders.get(0); + + // Try to set role holder as VoiceInteractionService + List<ResolveInfo> services = mPm.queryIntentServicesAsUser( + new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg), + PackageManager.GET_META_DATA, user.getIdentifier()); + + for (ResolveInfo resolveInfo : services) { + ServiceInfo serviceInfo = resolveInfo.serviceInfo; + + VoiceInteractionServiceInfo voiceInteractionServiceInfo = + new VoiceInteractionServiceInfo(mPm, serviceInfo); + if (!voiceInteractionServiceInfo.getSupportsAssist()) { + continue; + } + + String serviceComponentName = serviceInfo.getComponentName() + .flattenToShortString(); + + String serviceRecognizerName = new ComponentName(pkg, + voiceInteractionServiceInfo.getRecognitionService()) + .flattenToShortString(); + + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ASSISTANT, serviceComponentName); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName); + + return; + } + + // If no service could be found try to set assist activity + final List<ResolveInfo> activities = mPm.queryIntentActivitiesAsUser( + new Intent(Intent.ACTION_ASSIST).setPackage(pkg), + PackageManager.MATCH_DEFAULT_ONLY, user.getIdentifier()); + + for (ResolveInfo resolveInfo : activities) { + ActivityInfo activityInfo = resolveInfo.activityInfo; + + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ASSISTANT, + activityInfo.getComponentName().flattenToShortString()); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_INTERACTION_SERVICE, ""); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE, + getDefaultRecognizer(user)); + } + } + } + } + class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { super(handler); diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java index c2e4581285fd..acf994610182 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java +++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java @@ -24,9 +24,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; -// TODO: fix this. either move this class into system server or add a dependency on -// these wm classes to libiorap-java and libiorap-java-tests (somehow). import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; @@ -113,12 +112,12 @@ public abstract class AppLaunchEvent implements Parcelable { @Override protected void writeToParcelImpl(Parcel p, int flags) { super.writeToParcelImpl(p, flags); - intent.writeToParcel(p, flags); + IntentProtoParcelable.write(p, intent, flags); } IntentStarted(Parcel p) { super(p); - intent = Intent.CREATOR.createFromParcel(p); + intent = IntentProtoParcelable.create(p); } } @@ -232,8 +231,7 @@ public abstract class AppLaunchEvent implements Parcelable { } public static class ActivityLaunchCancelled extends AppLaunchEvent { - public final @Nullable - @ActivityRecordProto byte[] activityRecordSnapshot; + public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot; public ActivityLaunchCancelled(@SequenceId long sequenceId, @Nullable @ActivityRecordProto byte[] snapshot) { @@ -352,7 +350,6 @@ public abstract class AppLaunchEvent implements Parcelable { ActivityLaunchCancelled.class, }; - // TODO: move to @ActivityRecordProto byte[] once we have unit tests. public static class ActivityRecordProtoParcelable { public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot, int flags) { @@ -365,4 +362,31 @@ public abstract class AppLaunchEvent implements Parcelable { return data; } } + + public static class IntentProtoParcelable { + private static final int INTENT_PROTO_CHUNK_SIZE = 1024; + + public static void write(Parcel p, @NonNull Intent intent, int flags) { + // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream, + // so create a new one every time. + final ProtoOutputStream protoOutputStream = + new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE); + // Write this data out as the top-most IntentProto (i.e. it is not a sub-object). + intent.writeToProto(protoOutputStream); + final byte[] bytes = protoOutputStream.getBytes(); + + p.writeByteArray(bytes); + } + + // TODO: Should be mockable for testing? + // We cannot deserialize in the platform because we don't have a 'readFromProto' + // code. + public static @NonNull Intent create(Parcel p) { + // This will "read" the correct amount of data, but then we discard it. + byte[] data = p.createByteArray(); + + // Never called by real code in a platform, this binder API is implemented only in C++. + return new Intent("<cannot deserialize IntentProto>"); + } + } } diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java index 7fcad360b8fe..9a30b35f02a2 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java @@ -24,12 +24,16 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Handler; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityMetricsLaunchObserver; @@ -43,10 +47,20 @@ import com.android.server.wm.ActivityTaskManagerInternal; */ public class IorapForwardingService extends SystemService { - public static final boolean DEBUG = true; // TODO: read from a getprop? public static final String TAG = "IorapForwardingService"; + /** $> adb shell 'setprop log.tag.IorapdForwardingService VERBOSE' */ + public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + /** $> adb shell 'setprop iorapd.enable true' */ + private static boolean IS_ENABLED = SystemProperties.getBoolean("iorapd.enable", true); + /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ + private static boolean WTF_CRASH = SystemProperties.getBoolean( + "iorapd.forwarding_service.wtf_crash", false); private IIorap mIorapRemote; + private final Object mLock = new Object(); + /** Handle onBinderDeath by periodically trying to reconnect. */ + private final Handler mHandler = + new BinderConnectionHandler(IoThread.getHandler().getLooper()); /** * Initializes the system service. @@ -58,7 +72,7 @@ public class IorapForwardingService extends SystemService { * @param context The system server context. */ public IorapForwardingService(Context context) { - super(context); + super(context); } //<editor-fold desc="Providers"> @@ -78,12 +92,40 @@ public class IorapForwardingService extends SystemService { @VisibleForTesting protected IIorap provideIorapRemote() { + IIorap iorap; try { - return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); + iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); } catch (ServiceManager.ServiceNotFoundException e) { - // TODO: how do we handle service being missing? - throw new AssertionError(e); + handleRemoteError(e); + return null; } + + try { + iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0); + } catch (RemoteException e) { + handleRemoteError(e); + return null; + } + + return iorap; + } + + @VisibleForTesting + protected DeathRecipient provideDeathRecipient() { + return new DeathRecipient() { + @Override + public void binderDied() { + Log.w(TAG, "iorapd has died"); + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + }; + } + + @VisibleForTesting + protected boolean isIorapEnabled() { + // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process + // never comes up, so all binder connections will fail indefinitely. + return IS_ENABLED; } //</editor-fold> @@ -94,15 +136,128 @@ public class IorapForwardingService extends SystemService { Log.v(TAG, "onStart"); } + retryConnectToRemoteAndConfigure(/*attempts*/0); + } + + private class BinderConnectionHandler extends Handler { + public BinderConnectionHandler(android.os.Looper looper) { + super(looper); + } + + public static final int MESSAGE_BINDER_CONNECT = 0; + + private int mAttempts = 0; + + @Override + public void handleMessage(android.os.Message message) { + switch (message.what) { + case MESSAGE_BINDER_CONNECT: + if (!retryConnectToRemoteAndConfigure(mAttempts)) { + mAttempts++; + } else { + mAttempts = 0; + } + break; + default: + throw new AssertionError("Unknown message: " + message.toString()); + } + } + } + + /** + * Handle iorapd shutdowns and crashes, by attempting to reconnect + * until the service is reached again. + * + * <p>The first connection attempt is synchronous, + * subsequent attempts are done by posting delayed tasks to the IoThread.</p> + * + * @return true if connection succeeded now, or false if it failed now [and needs to requeue]. + */ + private boolean retryConnectToRemoteAndConfigure(int attempts) { + final int sleepTime = 1000; // ms + + if (DEBUG) { + Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts); + } + + if (connectToRemoteAndConfigure()) { + return true; + } + + // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually + // called 'adb shell stop iorapd' , which means this would loop until it comes back + // up. + // + // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid + // printing this warning. + Log.w(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime); + + // Use a handler instead of Thread#sleep to avoid backing up the binder thread + // when this is called from the death recipient callback. + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT), + sleepTime); + + return false; + + // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts"); + } + + private boolean connectToRemoteAndConfigure() { + synchronized (mLock) { + // Synchronize against any concurrent calls to this via the DeathRecipient. + return connectToRemoteAndConfigureLocked(); + } + } + + private boolean connectToRemoteAndConfigureLocked() { + if (!isIorapEnabled()) { + if (DEBUG) { + Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work"); + } + // When we see that iorapd is disabled (when system server comes up), + // it stays disabled permanently until the next system server reset. + + // TODO: consider listening to property changes as a callback, then we can + // be more dynamic about handling enable/disable. + return true; + } + // Connect to the native binder service. mIorapRemote = provideIorapRemote(); + if (mIorapRemote == null) { + Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?"); + return false; + } invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) ); + registerInProcessListenersLocked(); + + return true; + } + + private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); + private boolean mRegisteredListeners = false; + + private void registerInProcessListenersLocked() { + if (mRegisteredListeners) { + // Listeners are registered only once (idempotent operation). + // + // Today listeners are tolerant of the remote side going away + // by handling remote errors. + // + // We could try to 'unregister' the listener when we get a binder disconnect, + // but we'd still have to handle the case of encountering synchronous errors so + // it really wouldn't be a win (other than having less log spew). + return; + } // Listen to App Launch Sequence events from ActivityTaskManager, // and forward them to the native binder service. ActivityMetricsLaunchObserverRegistry launchObserverRegistry = provideLaunchObserverRegistry(); - launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver()); + launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); + + mRegisteredListeners = true; } private class AppLaunchObserver implements ActivityMetricsLaunchObserver { @@ -110,6 +265,8 @@ public class IorapForwardingService extends SystemService { // launch sequences on the native side. private @AppLaunchEvent.SequenceId long mSequenceId = -1; + // All callbacks occur on the same background thread. Don't synchronize explicitly. + @Override public void onIntentStarted(@NonNull Intent intent) { // #onIntentStarted [is the only transition that] initiates a new launch sequence. @@ -174,7 +331,7 @@ public class IorapForwardingService extends SystemService { invokeRemote(() -> mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity)) + new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity)) ); } } @@ -201,6 +358,7 @@ public class IorapForwardingService extends SystemService { } } + /** Allow passing lambdas to #invokeRemote */ private interface RemoteRunnable { void run() throws RemoteException; } @@ -209,8 +367,26 @@ public class IorapForwardingService extends SystemService { try { r.run(); } catch (RemoteException e) { - // TODO: what do we do with exceptions? - throw new AssertionError("not implemented", e); + // This could be a logic error (remote side returning error), which we need to fix. + // + // This could also be a DeadObjectException in which case its probably just iorapd + // being manually restarted. + // + // Don't make any assumption, since DeadObjectException could also mean iorapd crashed + // unexpectedly. + // + // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath. + handleRemoteError(e); } } + + private static void handleRemoteError(Throwable t) { + if (WTF_CRASH) { + // In development modes, we just want to crash. + throw new AssertionError("unexpected remote error", t); + } else { + // Log to wtf which gets sent to dropbox, and in system_server this does not crash. + Log.wtf(TAG, t); + } + } } diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml index f83a16ec0916..919154d3e48a 100644 --- a/startop/iorap/tests/AndroidTest.xml +++ b/startop/iorap/tests/AndroidTest.xml @@ -33,6 +33,15 @@ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"> </target_preparer> + <target_preparer + class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- Crash instead of using Log.wtf within the system_server iorap code. --> + <option name="set-property" key="iorapd.forwarding_service.wtf_crash" value="true" /> + <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status --> + <option name="set-property" key="iorapd.binder.fake" value="true" /> + <option name="restore-properties" value="true" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.google.android.startop.iorap.tests" /> <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java index 2b99ce1d8252..2d29875aadb4 100644 --- a/telephony/java/android/telephony/CallAttributes.java +++ b/telephony/java/android/telephony/CallAttributes.java @@ -50,10 +50,10 @@ public class CallAttributes implements Parcelable { } private CallAttributes(Parcel in) { - mPreciseCallState = (PreciseCallState) in.readValue(mPreciseCallState.getClass() - .getClassLoader()); + mPreciseCallState = (PreciseCallState) + in.readValue(PreciseCallState.class.getClassLoader()); mNetworkType = in.readInt(); - mCallQuality = (CallQuality) in.readValue(mCallQuality.getClass().getClassLoader()); + mCallQuality = (CallQuality) in.readValue(CallQuality.class.getClassLoader()); } // getters diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a33b44c454b4..349880d5ea3c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -608,11 +608,41 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL = "carrier_promote_wfc_on_call_fail_bool"; - /** Flag specifying whether provisioning is required for VOLTE. */ + /** + * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi + * Calling. + */ public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; /** + * Flag indicating whether or not the IMS MmTel UT capability requires carrier provisioning + * before it can be set as enabled. + * + * If true, the UT capability will be set to false for the newly loaded subscription + * and will require the carrier provisioning app to set the persistent provisioning result. + * If false, the platform will not wait for provisioning status updates for the UT capability + * and enable the UT over IMS capability for the subscription when the subscription is loaded. + * + * The default value for this key is {@code false}. + */ + public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = + "carrier_ut_provisioning_required_bool"; + + /** + * Flag indicating whether or not the carrier supports Supplementary Services over the UT + * interface for this subscription. + * + * If true, the device will use Supplementary Services over UT when provisioned (see + * {@link #KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL}). If false, this device will fallback to + * circuit switch for supplementary services and will disable this capability for IMS entirely. + * + * The default value for this key is {@code true}. + */ + public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = + "carrier_supports_ss_over_ut_bool"; + + /** * Flag specifying if WFC provisioning depends on VoLTE provisioning. * * {@code false}: default value; honor actual WFC provisioning state. @@ -2575,6 +2605,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2); sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true); diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 0fa1b41d4b16..bca088e618c0 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -91,20 +91,6 @@ public class EuiccManager { "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE"; /** - * Intent action to select a profile to enable before download a new eSIM profile. - * - * May be called during device provisioning when there are multiple slots having profiles on - * them. This Intent launches a screen for all the current existing profiles and let users to - * choose which one they want to enable. In this case, the slot contains the profile will be - * activated. - * - * @hide - */ - @SystemApi - public static final String ACTION_PROFILE_SELECTION = - "android.telephony.euicc.action.PROFILE_SELECTION"; - - /** * Intent action to provision an embedded subscription. * * <p>May be called during device provisioning to launch a screen to perform embedded SIM @@ -325,8 +311,8 @@ public class EuiccManager { @IntDef(prefix = {"EUICC_ACTIVATION_"}, value = { EUICC_ACTIVATION_TYPE_DEFAULT, EUICC_ACTIVATION_TYPE_BACKUP, - EUICC_ACTIVATION_TYPE_TRANSFER - + EUICC_ACTIVATION_TYPE_TRANSFER, + EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED, }) public @interface EuiccActivationType{} @@ -360,6 +346,14 @@ public class EuiccManager { @SystemApi public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; + /** + * The activation flow of eSIM requiring user account will be started. This can only be used + * when there is user account signed in. Otherwise, the flow will be failed. + * + * @hide + */ + @SystemApi + public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; /** * Euicc OTA update status which can be got by {@link #getOtaStatus} diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 9414abd98b1c..5b2e635b179f 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -86,9 +86,7 @@ public class ImsMmTelManager { /** * Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough. - * @hide */ - @SystemApi public static final int WIFI_MODE_WIFI_PREFERRED = 2; /** diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index d37198a3e25d..086a76546b2d 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -21,13 +21,17 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.WorkerThread; import android.content.Context; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.ims.aidl.IImsConfigCallback; +import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsConfigImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import com.android.internal.telephony.ITelephony; @@ -38,13 +42,68 @@ import java.util.concurrent.Executor; * to changes in these configurations. * * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning - * applications and may vary. + * applications and may vary. For compatibility purposes, the first 100 integer values used in + * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys + * previously defined in the Android framework. Some common constants have been defined in this + * class to make integrating with other system apps easier. USE WITH CARE! + * + * To avoid collisions, please use String based configurations when possible: + * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}. * @hide */ @SystemApi public class ProvisioningManager { /** + * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error. + */ + public static final String STRING_QUERY_RESULT_ERROR_GENERIC = + "STRING_QUERY_RESULT_ERROR_GENERIC"; + + /** + * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the + * ImsService implementation was not ready for provisioning queries. + */ + public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = + "STRING_QUERY_RESULT_ERROR_NOT_READY"; + + /** + * The integer result of provisioning for the queried key is disabled. + */ + public static final int PROVISIONING_VALUE_DISABLED = 0; + + /** + * The integer result of provisioning for the queried key is enabled. + */ + public static final int PROVISIONING_VALUE_ENABLED = 1; + + + /** + * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in + * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning + * the subscription for WiFi Calling. + * + * @see #getProvisioningIntValue(int) + * @see #setProvisioningIntValue(int, int) + */ + public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; + + /** + * Override the user-defined WiFi mode for this subscription, defined in + * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning + * this subscription for WiFi Calling. + * + * Valid values for this key are: + * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY}, + * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or + * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}. + * + * @see #getProvisioningIntValue(int) + * @see #setProvisioningIntValue(int, int) + */ + public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; + + /** * Callback for IMS provisioning changes. */ public static class Callback { @@ -180,10 +239,15 @@ public class ProvisioningManager { /** * Query for the integer value associated with the provided key. + * + * This operation is blocking and should not be performed on the UI thread. + * * @param key An integer that represents the provisioning key, which is defined by the OEM. - * @return an integer value for the provided key. + * @return an integer value for the provided key, or + * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist. * @throws IllegalArgumentException if the key provided was invalid. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int key) { try { @@ -195,10 +259,16 @@ public class ProvisioningManager { /** * Query for the String value associated with the provided key. - * @param key An integer that represents the provisioning key, which is defined by the OEM. - * @return a String value for the provided key, or {@code null} if the key doesn't exist. + * + * This operation is blocking and should not be performed on the UI thread. + * + * @param key A String that represents the provisioning key, which is defined by the OEM. + * @return a String value for the provided key, {@code null} if the key doesn't exist, or one + * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC}, + * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}. * @throws IllegalArgumentException if the key provided was invalid. */ + @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int key) { try { @@ -210,10 +280,16 @@ public class ProvisioningManager { /** * Set the integer value associated with the provided key. + * + * This operation is blocking and should not be performed on the UI thread. + * + * Use {@link #setProvisioningStringValue(int, String)} with proper namespacing (to be defined + * per OEM or carrier) when possible instead to avoid key collision if needed. * @param key An integer that represents the provisioning key, which is defined by the OEM. * @param value a integer value for the provided key. * @return the result of setting the configuration value. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) { try { @@ -226,10 +302,14 @@ public class ProvisioningManager { /** * Set the String value associated with the provided key. * - * @param key An integer that represents the provisioning key, which is defined by the OEM. + * This operation is blocking and should not be performed on the UI thread. + * + * @param key A String that represents the provisioning key, which is defined by the OEM and + * should be appropriately namespaced to avoid collision. * @param value a String value for the provided key. * @return the result of setting the configuration value. */ + @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key, String value) { @@ -240,6 +320,55 @@ public class ProvisioningManager { } } + /** + * Set the provisioning status for the IMS MmTel capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise. + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setProvisioningStatusForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) { + try { + getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech, + isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Get the provisioning status for the IMS MmTel capability specified. + * + * If provisioning is not required for the queried + * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and + * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will + * always return {@code true}. + * + * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean getProvisioningStatusForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().getImsProvisioningStatusForCapability(mSubId, capability, tech); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + private static SubscriptionManager getSubscriptionManager(Context context) { SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); if (manager == null) { diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java index 7c793a5c18ac..1ee85633c6dc 100644 --- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java @@ -97,6 +97,13 @@ public final class CapabilityChangeRequest implements Parcelable { public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() { return radioTech; } + + @Override + public String toString() { + return "CapabilityPair{" + + "mCapability=" + mCapability + + ", radioTech=" + radioTech + '}'; + } } // Pair contains <radio tech, mCapability> @@ -212,6 +219,13 @@ public final class CapabilityChangeRequest implements Parcelable { } } + @Override + public String toString() { + return "CapabilityChangeRequest{" + + "mCapabilitiesToEnable=" + mCapabilitiesToEnable + + ", mCapabilitiesToDisable=" + mCapabilitiesToDisable + '}'; + } + /** * @hide */ diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index 71a21743a449..4fc6a19d1f38 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -277,12 +277,14 @@ public class ImsConfig { * Wi-Fi calling roaming status. * Value is in Integer format. ON (1), OFF(0). */ - public static final int VOICE_OVER_WIFI_ROAMING = 26; + public static final int VOICE_OVER_WIFI_ROAMING = + ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE; /** * Wi-Fi calling modem - WfcModeFeatureValueConstants. * Value is in Integer format. */ - public static final int VOICE_OVER_WIFI_MODE = 27; + public static final int VOICE_OVER_WIFI_MODE = + ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE; /** * VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0). * Value is in Integer format. diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 9cc173cfcdd6..8237d39c4c3d 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1765,6 +1765,24 @@ interface ITelephony { void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback); /** + * Set the provisioning status for the IMS MmTel capability using the specified subscription. + */ + void setImsProvisioningStatusForCapability(int subId, int capability, int tech, + boolean isProvisioned); + + /** + * Get the provisioning status for the IMS MmTel capability specified. + */ + boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech); + + /** Is the capability and tech flagged as provisioned in the cache */ + boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); + + /** Set the provisioning for the capability and tech in the cache */ + void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech, + boolean isProvisioned); + + /** * Return an integer containing the provisioning value for the specified provisioning key. */ int getImsProvisioningInt(int subId, int key); diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index 0edc0026722b..c76d153c6112 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java @@ -284,10 +284,6 @@ public final class TelephonyPermissions { */ private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid, int uid, String callingPackage, String message) { - // If the device identifier check is enabled then enforce the new access requirements for - // both 1P and 3P apps. - boolean enableDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, 0) == 1; // Check if the application is a 3P app; if so then a separate setting is required to relax // the check to begin flagging problems with 3P apps early. boolean relax3PDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(), @@ -300,6 +296,11 @@ public final class TelephonyPermissions { context.getContentResolver(), Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED, 0) == 1; boolean isNonPrivApp = false; + // Similar to above support relaxing the check for privileged apps while still enforcing it + // for non-privileged and 3P apps. + boolean relaxPrivDeviceIdentifierCheck = Settings.Global.getInt( + context.getContentResolver(), + Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED, 0) == 1; ApplicationInfo callingPackageInfo = null; try { callingPackageInfo = context.getPackageManager().getApplicationInfo(callingPackage, 0); @@ -315,37 +316,28 @@ public final class TelephonyPermissions { Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage, e); } - Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message - + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp); - // The new Q restrictions for device identifier access will be enforced if any of the - // following are true: - // - The PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED setting has been set. - // - The app requesting a device identifier is not a preloaded app (3P), and the - // PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED setting has not been set. - // - The app requesting a device identifier is a preloaded app but is not a privileged app, - // and the PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED setting has not been set. - if (enableDeviceIdentifierCheck + // The new Q restrictions for device identifier access will be enforced for all apps with + // settings to individually disable the new restrictions for privileged, preloaded + // non-privileged, and 3P apps. + if ((!is3PApp && !isNonPrivApp && !relaxPrivDeviceIdentifierCheck) || (is3PApp && !relax3PDeviceIdentifierCheck) || (isNonPrivApp && !relaxNonPrivDeviceIdentifierCheck)) { - boolean targetQBehaviorDisabled = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, 0) == 0; - if (callingPackage != null) { - // if the target SDK is pre-Q or the target Q behavior is disabled then check if - // the calling package would have previously had access to device identifiers. - if (callingPackageInfo != null && ( - callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q - || targetQBehaviorDisabled)) { - if (context.checkPermission( - android.Manifest.permission.READ_PHONE_STATE, - pid, - uid) == PackageManager.PERMISSION_GRANTED) { - return false; - } - if (SubscriptionManager.isValidSubscriptionId(subId) - && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { - return false; - } + Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp); + // if the target SDK is pre-Q then check if the calling package would have previously + // had access to device identifiers. + if (callingPackageInfo != null && ( + callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) { + if (context.checkPermission( + android.Manifest.permission.READ_PHONE_STATE, + pid, + uid) == PackageManager.PERMISSION_GRANTED) { + return false; + } + if (SubscriptionManager.isValidSubscriptionId(subId) + && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return false; } } throw new SecurityException(message + ": The user " + uid diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java index 07c4a938cf9f..c16efbda1830 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -70,6 +70,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { R.id.benchmark_text_low_hitrate, R.id.benchmark_edit_text_input, R.id.benchmark_overdraw, + R.id.benchmark_bitmap_upload, }; public static class LocalBenchmarksList extends ListFragment { @@ -204,6 +205,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { case R.id.benchmark_text_low_hitrate: case R.id.benchmark_edit_text_input: case R.id.benchmark_overdraw: + case R.id.benchmark_bitmap_upload: case R.id.benchmark_memory_bandwidth: case R.id.benchmark_memory_latency: case R.id.benchmark_power_management: @@ -323,6 +325,9 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { intent = new Intent(getApplicationContext(), EditTextInputActivity.class); break; case R.id.benchmark_overdraw: + intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class); + break; + case R.id.benchmark_bitmap_upload: intent = new Intent(getApplicationContext(), BitmapUploadActivity.class); break; case R.id.benchmark_memory_bandwidth: diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java index 89c6aedd8b5c..5723c599d91a 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java @@ -229,6 +229,8 @@ public class BenchmarkRegistry { return context.getString(R.string.cpu_gflops_name); case R.id.benchmark_overdraw: return context.getString(R.string.overdraw_name); + case R.id.benchmark_bitmap_upload: + return context.getString(R.string.bitmap_upload_name); default: return "Some Benchmark"; } diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java index 787090208d7e..7692836cfacc 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.View; import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkRegistry; import com.android.benchmark.ui.automation.Automator; import com.android.benchmark.ui.automation.Interaction; @@ -124,7 +125,9 @@ public class BitmapUploadActivity extends AppCompatActivity { final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); - mAutomator = new Automator("BMUpload", runId, iteration, getWindow(), + String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload); + + mAutomator = new Automator(name, runId, iteration, getWindow(), new Automator.AutomateCallback() { @Override public void onPostAutomate() { diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml index 6801fd9f61ec..694e0d9a6917 100644 --- a/tests/JankBench/app/src/main/res/values/ids.xml +++ b/tests/JankBench/app/src/main/res/values/ids.xml @@ -23,6 +23,7 @@ <item name="benchmark_text_low_hitrate" type="id" /> <item name="benchmark_edit_text_input" type="id" /> <item name="benchmark_overdraw" type="id" /> + <item name="benchmark_bitmap_upload" type="id" /> <item name="benchmark_memory_bandwidth" type="id" /> <item name="benchmark_memory_latency" type="id" /> <item name="benchmark_power_management" type="id" /> diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml index 270adf89e4ed..5c2405899db9 100644 --- a/tests/JankBench/app/src/main/res/values/strings.xml +++ b/tests/JankBench/app/src/main/res/values/strings.xml @@ -33,6 +33,8 @@ <string name="edit_text_input_description">Tests edit text input</string> <string name="overdraw_name">Overdraw Test</string> <string name="overdraw_description">Tests how the device handles overdraw</string> + <string name="bitmap_upload_name">Bitmap Upload Test</string> + <string name="bitmap_upload_description">Tests bitmap upload</string> <string name="memory_bandwidth_name">Memory Bandwidth</string> <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string> <string name="memory_latency_name">Memory Latency</string> diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml index 07c453c25359..fccc7b9d3776 100644 --- a/tests/JankBench/app/src/main/res/xml/benchmark.xml +++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml @@ -62,6 +62,12 @@ benchmark:category="ui" benchmark:description="@string/overdraw_description" /> + <com.android.benchmark.Benchmark + benchmark:name="@string/bitmap_upload_name" + benchmark:id="@id/benchmark_bitmap_upload" + benchmark:category="ui" + benchmark:description="@string/bitmap_upload_description" /> + <!-- <com.android.benchmark.Benchmark benchmark:name="@string/memory_bandwidth_name" diff --git a/tests/RollbackTest/Android.mk b/tests/RollbackTest/Android.mk index 34aa258bf465..780bb24e437b 100644 --- a/tests/RollbackTest/Android.mk +++ b/tests/RollbackTest/Android.mk @@ -36,6 +36,17 @@ LOCAL_PACKAGE_NAME := RollbackTestAppAv2 include $(BUILD_PACKAGE) ROLLBACK_TEST_APP_AV2 := $(LOCAL_INSTALLED_MODULE) +# RollbackTestAppACrashingV2.apk +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src) +LOCAL_MANIFEST_FILE := TestApp/ACrashingV2.xml +LOCAL_PACKAGE_NAME := RollbackTestAppACrashingV2 +include $(BUILD_PACKAGE) +ROLLBACK_TEST_APP_A_CRASHING_V2 := $(LOCAL_INSTALLED_MODULE) + # RollbackTestAppBv1.apk include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional @@ -68,6 +79,7 @@ LOCAL_COMPATIBILITY_SUITE := general-tests LOCAL_JAVA_RESOURCE_FILES := \ $(ROLLBACK_TEST_APP_AV1) \ $(ROLLBACK_TEST_APP_AV2) \ + $(ROLLBACK_TEST_APP_A_CRASHING_V2) \ $(ROLLBACK_TEST_APP_BV1) \ $(ROLLBACK_TEST_APP_BV2) LOCAL_SDK_VERSION := system_current @@ -77,5 +89,6 @@ include $(BUILD_PACKAGE) # Clean up local variables ROLLBACK_TEST_APP_AV1 := ROLLBACK_TEST_APP_AV2 := +ROLLBACK_TEST_APP_A_CRASHING_V2 := ROLLBACK_TEST_APP_BV1 := ROLLBACK_TEST_APP_BV2 := diff --git a/tests/RollbackTest/TestApp/ACrashingV2.xml b/tests/RollbackTest/TestApp/ACrashingV2.xml new file mode 100644 index 000000000000..5708d2385f01 --- /dev/null +++ b/tests/RollbackTest/TestApp/ACrashingV2.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tests.rollback.testapp.A" + android:versionCode="2" + android:versionName="2.0" > + + + <uses-sdk android:minSdkVersion="19" /> + + <application android:label="Rollback Test App A v2"> + <meta-data android:name="version" android:value="2" /> + <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" + android:exported="true" /> + <activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java new file mode 100644 index 000000000000..02a439b5dd69 --- /dev/null +++ b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback.testapp; + +import android.app.Activity; +import android.os.Bundle; + +/** + * A crashing test app for testing apk rollback support. + */ +public class CrashingMainActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + throw new RuntimeException("Intended force crash"); + } +} diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 9d67cea05fc8..13ac4f09dd86 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -38,6 +38,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -57,6 +58,7 @@ public class RollbackTest { private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A"; private static final String TEST_APP_B = "com.android.tests.rollback.testapp.B"; + private static final String INSTRUMENTED_APP = "com.android.tests.rollback"; /** * Test basic rollbacks. @@ -663,4 +665,63 @@ public class RollbackTest { assertEquals(packageName, info.getVersionRolledBackTo().getPackageName()); assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode()); } + + // TODO(zezeozue): Stop ignoring after fixing race between rolling back and testing version + /** + * Test bad update automatic rollback. + */ + @Ignore("Flaky") + @Test + public void testBadUpdateRollback() throws Exception { + try { + RollbackTestUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.MANAGE_ROLLBACKS); + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + + // Prep installation of the test apps. + RollbackTestUtils.uninstall(TEST_APP_A); + RollbackTestUtils.install("RollbackTestAppAv1.apk", false); + RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true); + assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + + RollbackTestUtils.uninstall(TEST_APP_B); + RollbackTestUtils.install("RollbackTestAppBv1.apk", false); + RollbackTestUtils.install("RollbackTestAppBv2.apk", true); + assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + + // Both test apps should now be available for rollback, and the + // targetPackage returned for rollback should be correct. + // TODO: See if there is a way to remove this race condition + // between when the app is installed and when the rollback + // is made available. + Thread.sleep(1000); + RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A); + assertNotNull(rollbackA); + assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName()); + + RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B); + assertNotNull(rollbackB); + assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName()); + + // Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes + for (int i = 0; i < 5; i++) { + RollbackTestUtils.launchPackage(TEST_APP_A); + Thread.sleep(1000); + } + Thread.sleep(1000); + + // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver + assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + // Instrumented app is still the package installer + Context context = InstrumentationRegistry.getContext(); + String installer = context.getPackageManager().getInstallerPackageName(TEST_APP_A); + assertEquals(INSTRUMENTED_APP, installer); + // TEST_APP_B is untouched + assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + } finally { + RollbackTestUtils.dropShellPermissionIdentity(); + } + } } diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java index fbc3d8f1cd34..edb13556b8fc 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java @@ -135,6 +135,17 @@ class RollbackTestUtils { assertStatusSuccess(LocalIntentSender.getIntentSenderResult()); } + /** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */ + static void launchPackage(String packageName) + throws InterruptedException, IOException { + Context context = InstrumentationRegistry.getContext(); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setPackage(packageName); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + context.startActivity(intent); + } + /** * Installs the apks with the given resource names as an atomic set. * diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b5d5f61b527e..923c7dd5fb94 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -123,6 +123,7 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkUtils; +import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.SocketKeepalive; import android.net.UidRange; @@ -161,6 +162,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; @@ -1007,6 +1009,11 @@ public class ConnectivityServiceTest { } @Override + protected ProxyTracker makeProxyTracker() { + return mock(ProxyTracker.class); + } + + @Override protected int reserveNetId() { while (true) { final int netId = super.reserveNetId(); @@ -1028,6 +1035,11 @@ public class ConnectivityServiceTest { } } + @Override + protected boolean queryUserAccess(int uid, int netId) { + return true; + } + public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) { return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; } @@ -5132,4 +5144,84 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(lp); verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES); } + + @Test + public void testGetGlobalProxyForNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + final Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo); + assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork)); + } + + @Test + public void testGetProxyForActiveNetwork() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + final LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } + + @Test + public void testGetProxyForVPN() { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + + // Set up a WiFi network with no proxy + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Set up a VPN network with a proxy + final int uid = Process.myUid(); + final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); + final ArraySet<UidRange> ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); + mMockVpn.setUids(ranges); + LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + // Connect to VPN with proxy + mMockVpn.setNetworkAgent(vpnNetworkAgent); + vpnNetworkAgent.connect(true); + mMockVpn.connect(); + waitForIdle(); + + // Test that the VPN network returns a proxy, and the WiFi does not. + assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + + // Test that the VPN network returns no proxy when it is set to null. + testLinkProperties.setHttpProxy(null); + vpnNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertNull(mService.getProxyForNetwork(null)); + + // Set WiFi proxy and check that the vpn proxy is still null. + testLinkProperties.setHttpProxy(testProxyInfo); + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Disconnect from VPN and check that the active network, which is now the WiFi, has the + // correct proxy setting. + vpnNetworkAgent.disconnect(); + waitForIdle(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } } diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java index f2ecef95b599..e57433a52cca 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -202,9 +202,11 @@ public class IpMemoryStoreServiceTest { final CountDownLatch latch = new CountDownLatch(1); functor.accept(latch); try { - latch.await(5000, TimeUnit.MILLISECONDS); + if (!latch.await(5000, TimeUnit.MILLISECONDS)) { + fail(timeoutMessage); + } } catch (InterruptedException e) { - fail(timeoutMessage); + fail("Thread was interrupted"); } } @@ -314,6 +316,7 @@ public class IpMemoryStoreServiceTest { assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); assertNull(key); assertNull(attr); + latch.countDown(); }))); } @@ -383,6 +386,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[5], key); + latch.countDown(); }))); // MTU matches key 4 but v4 address matches key 5. The latter is stronger. @@ -392,6 +396,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[5], key); + latch.countDown(); }))); // Closest to key 3 (indeed, identical) @@ -402,6 +407,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[3], key); + latch.countDown(); }))); // Group hint alone must not be strong enough to override the rest @@ -411,6 +417,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[3], key); + latch.countDown(); }))); // Still closest to key 3, though confidence is lower @@ -421,6 +428,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[3], key); + latch.countDown(); }))); // But changing the MTU makes this closer to key 4 @@ -430,6 +438,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(FAKE_KEYS[4], key); + latch.countDown(); }))); // MTU alone not strong enough to make this group-close @@ -441,6 +450,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertNull(key); + latch.countDown(); }))); } @@ -450,6 +460,7 @@ public class IpMemoryStoreServiceTest { assertTrue("Retrieve network sameness not successful : " + status.resultCode, status.isSuccess()); assertEquals(sameness, answer.getNetworkSameness()); + latch.countDown(); }))); } @@ -488,6 +499,7 @@ public class IpMemoryStoreServiceTest { + status.resultCode, status.isSuccess()); assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); assertNull(answer); + latch.countDown(); }))); } } diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp index 9b5df56e3987..06ff05e5d755 100644 --- a/tools/processors/view_inspector/Android.bp +++ b/tools/processors/view_inspector/Android.bp @@ -1,6 +1,8 @@ -java_library_host { +java_plugin { name: "view-inspector-annotation-processor", + processor_class: "android.processor.view.inspector.PlatformInspectableProcessor", + srcs: ["src/java/**/*.java"], java_resource_dirs: ["src/resources"], diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index a0ce9dde3068..d5497990aefd 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -214,4 +214,6 @@ interface IWifiManager in IDppCallback callback); void stopDppSession(); + + void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec); } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index d2d711f10944..96493de69673 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -787,6 +787,18 @@ public class WifiConfiguration implements Parcelable { public boolean trusted; /** + * This Wifi configuration is created from a {@link WifiNetworkSuggestion} + * @hide + */ + public boolean fromWifiNetworkSuggestion; + + /** + * This Wifi configuration is created from a {@link WifiNetworkSpecifier} + * @hide + */ + public boolean fromWifiNetworkSpecifier; + + /** * Indicates if the creator of this configuration has expressed that it * should be considered metered. * @@ -1668,6 +1680,8 @@ public class WifiConfiguration implements Parcelable { ephemeral = false; osu = false; trusted = true; // Networks are considered trusted by default. + fromWifiNetworkSuggestion = false; + fromWifiNetworkSpecifier = false; meteredHint = false; meteredOverride = METERED_OVERRIDE_NONE; useExternalScores = false; @@ -1779,10 +1793,13 @@ public class WifiConfiguration implements Parcelable { if (this.ephemeral) sbuf.append(" ephemeral"); if (this.osu) sbuf.append(" osu"); if (this.trusted) sbuf.append(" trusted"); + if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion"); + if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier"); if (this.meteredHint) sbuf.append(" meteredHint"); if (this.useExternalScores) sbuf.append(" useExternalScores"); if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess - || this.ephemeral || this.trusted || this.meteredHint || this.useExternalScores) { + || this.ephemeral || this.trusted || this.fromWifiNetworkSuggestion + || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) { sbuf.append("\n"); } if (this.meteredOverride != METERED_OVERRIDE_NONE) { @@ -2270,6 +2287,8 @@ public class WifiConfiguration implements Parcelable { ephemeral = source.ephemeral; osu = source.osu; trusted = source.trusted; + fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion; + fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier; meteredHint = source.meteredHint; meteredOverride = source.meteredOverride; useExternalScores = source.useExternalScores; @@ -2347,6 +2366,8 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(isLegacyPasspointConfig ? 1 : 0); dest.writeInt(ephemeral ? 1 : 0); dest.writeInt(trusted ? 1 : 0); + dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0); + dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0); dest.writeInt(meteredHint ? 1 : 0); dest.writeInt(meteredOverride); dest.writeInt(useExternalScores ? 1 : 0); @@ -2418,6 +2439,8 @@ public class WifiConfiguration implements Parcelable { config.isLegacyPasspointConfig = in.readInt() != 0; config.ephemeral = in.readInt() != 0; config.trusted = in.readInt() != 0; + config.fromWifiNetworkSuggestion = in.readInt() != 0; + config.fromWifiNetworkSpecifier = in.readInt() != 0; config.meteredHint = in.readInt() != 0; config.meteredOverride = in.readInt(); config.useExternalScores = in.readInt() != 0; diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 35fba3dcf7cf..488de8789178 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -16,6 +16,7 @@ package android.net.wifi; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.net.NetworkInfo.DetailedState; @@ -120,8 +121,14 @@ public class WifiInfo implements Parcelable { @UnsupportedAppUsage private String mMacAddress = DEFAULT_MAC_ADDRESS; + /** + * Whether the network is ephemeral or not. + */ private boolean mEphemeral; + /** + * Whether the network is trusted or not. + */ private boolean mTrusted; /** @@ -130,6 +137,12 @@ public class WifiInfo implements Parcelable { private boolean mOsuAp; /** + * If connected to a network suggestion or specifier, store the package name of the app, + * else null. + */ + private String mNetworkSuggestionOrSpecifierPackageName; + + /** * Running total count of lost (not ACKed) transmitted unicast data packets. * @hide */ @@ -209,6 +222,7 @@ public class WifiInfo implements Parcelable { setMeteredHint(false); setEphemeral(false); setOsuAp(false); + setNetworkSuggestionOrSpecifierPackageName(null); txBad = 0; txSuccess = 0; rxSuccess = 0; @@ -240,6 +254,8 @@ public class WifiInfo implements Parcelable { mMeteredHint = source.mMeteredHint; mEphemeral = source.mEphemeral; mTrusted = source.mTrusted; + mNetworkSuggestionOrSpecifierPackageName = + source.mNetworkSuggestionOrSpecifierPackageName; mOsuAp = source.mOsuAp; txBad = source.txBad; txRetries = source.txRetries; @@ -476,6 +492,17 @@ public class WifiInfo implements Parcelable { return mOsuAp; } + /** {@hide} */ + public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) { + mNetworkSuggestionOrSpecifierPackageName = packageName; + } + + /** {@hide} */ + public @Nullable String getNetworkSuggestionOrSpecifierPackageName() { + return mNetworkSuggestionOrSpecifierPackageName; + } + + /** @hide */ @UnsupportedAppUsage public void setNetworkId(int id) { @@ -634,6 +661,7 @@ public class WifiInfo implements Parcelable { dest.writeDouble(rxSuccessRate); mSupplicantState.writeToParcel(dest, flags); dest.writeInt(mOsuAp ? 1 : 0); + dest.writeString(mNetworkSuggestionOrSpecifierPackageName); } /** Implement the Parcelable interface {@hide} */ @@ -672,6 +700,7 @@ public class WifiInfo implements Parcelable { info.rxSuccessRate = in.readDouble(); info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in); info.mOsuAp = in.readInt() != 0; + info.mNetworkSuggestionOrSpecifierPackageName = in.readString(); return info; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 084bd0936725..066823931832 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -16,7 +16,7 @@ package android.net.wifi; -import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.ACCESS_WIFI_STATE; import static android.Manifest.permission.READ_WIFI_CREDENTIAL; @@ -950,8 +950,7 @@ public class WifiManager { * which was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag * set. * <p> - * Note: The broadcast is sent to the app only if it holds either one of - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or + * Note: The broadcast is sent to the app only if it holds * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission. * * @see #EXTRA_NETWORK_SUGGESTION @@ -1183,7 +1182,7 @@ public class WifiManager { * containing configurations which they created. */ @Deprecated - @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE}) + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE}) public List<WifiConfiguration> getConfiguredNetworks() { try { ParceledListSlice<WifiConfiguration> parceledList = @@ -1199,7 +1198,7 @@ public class WifiManager { /** @hide */ @SystemApi - @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL}) + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL}) public List<WifiConfiguration> getPrivilegedConfiguredNetworks() { try { ParceledListSlice<WifiConfiguration> parceledList = @@ -1405,7 +1404,6 @@ public class WifiManager { * {@link #reject()} to return the user's selection back to the platform via this callback. * @hide */ - @SystemApi public interface NetworkRequestUserSelectionCallback { /** * User selected this network to connect to. @@ -1429,7 +1427,6 @@ public class WifiManager { * or reject the request by the app. * @hide */ - @SystemApi public interface NetworkRequestMatchCallback { /** * Invoked to register a callback to be invoked to convey user selection. The callback @@ -1606,7 +1603,6 @@ public class WifiManager { * object. If null, then the application's main thread will be used. * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback, @Nullable Handler handler) { @@ -1636,7 +1632,6 @@ public class WifiManager { * @param callback Callback for network match events * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterNetworkRequestMatchCallback( @NonNull NetworkRequestMatchCallback callback) { @@ -1656,8 +1651,7 @@ public class WifiManager { * When the device decides to connect to one of the provided network suggestions, platform sends * a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if * the network was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} - * flag set and the app holds either one of - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or + * flag set and the app holds * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission. *<p> * NOTE: @@ -2290,7 +2284,6 @@ public class WifiManager { /** * Return the results of the latest access point scan. * @return the list of access points found in the most recent scan. An app must hold - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission * in order to get valid results. */ @@ -2601,7 +2594,7 @@ public class WifiManager { * <p> * Applications need to have the following permissions to start LocalOnlyHotspot: {@link * android.Manifest.permission#CHANGE_WIFI_STATE} and {@link - * android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}. Callers without + * android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. Callers without * the permissions will trigger a {@link java.lang.SecurityException}. * <p> * @param callback LocalOnlyHotspotCallback for the application to receive updates about @@ -2684,7 +2677,7 @@ public class WifiManager { * {@link LocalOnlyHotspotObserver#onStopped()} callbacks. * <p> * Applications should have the - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} * permission. Callers without the permission will trigger a * {@link java.lang.SecurityException}. * <p> @@ -4865,4 +4858,26 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } -} + + /** + * Provide a Wi-Fi usability score information to be recorded (but not acted upon) by the + * framework. The Wi-Fi usability score is derived from {@link WifiUsabilityStatsListener} + * where a score is matched to Wi-Fi usability statistics using the sequence number. The score + * is used to quantify whether Wi-Fi is usable in a future time. + * + * @param seqNum Sequence number of the Wi-Fi usability score. + * @param score The Wi-Fi usability score. + * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) + public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { + try { + mService.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +}
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java index 81a06e825439..a8b19b3e2f64 100644 --- a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java +++ b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java @@ -30,7 +30,7 @@ package android.net.wifi.aware; public class IdentityChangedListener { /** * @param mac The MAC address of the Aware discovery interface. The application must have the - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to get the actual MAC address, + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} to get the actual MAC address, * otherwise all 0's will be provided. */ public void onIdentityChanged(byte[] mac) { diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 1fa1fd521a8e..8aef7a2cc6b6 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -231,7 +231,7 @@ public class WifiAwareManager { * <p> * This version of the API attaches a listener to receive the MAC address of the Aware interface * on startup and whenever it is updated (it is randomized at regular intervals for privacy). - * The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} + * The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION} * permission to execute this attach request. Otherwise, use the * {@link #attach(AttachCallback, Handler)} version. Note that aside from permission * requirements this listener will wake up the host at regular intervals causing higher power diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java index 5f8841cb0148..245b3043d30a 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java @@ -133,7 +133,7 @@ public class WifiAwareSession implements AutoCloseable { * An application must use the {@link DiscoverySession#close()} to * terminate the publish discovery session once it isn't needed. This will free * resources as well terminate any on-air transmissions. - * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} + * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION} * permission to start a publish discovery session. * * @param publishConfig The {@link PublishConfig} specifying the @@ -179,7 +179,7 @@ public class WifiAwareSession implements AutoCloseable { * An application must use the {@link DiscoverySession#close()} to * terminate the subscribe discovery session once it isn't needed. This will free * resources as well terminate any on-air transmissions. - * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} + * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION} * permission to start a subscribe discovery session. * * @param subscribeConfig The {@link SubscribeConfig} specifying the diff --git a/wifi/java/android/net/wifi/aware/package.html b/wifi/java/android/net/wifi/aware/package.html index d5d962f629de..c4f2e1fec96b 100644 --- a/wifi/java/android/net/wifi/aware/package.html +++ b/wifi/java/android/net/wifi/aware/package.html @@ -15,7 +15,7 @@ <ul> <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li> <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li> - <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li> + <li>{@link android.Manifest.permission#ACCESS_FINE_LOCATION}</li> </ul> <p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi Aware diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 1bed914c7772..052ab99da905 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -1139,7 +1139,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener)); @@ -1183,7 +1183,7 @@ public class WifiP2pManager { * @param config options as described in {@link WifiP2pConfig} class * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(Channel c, WifiP2pConfig config, ActionListener listener) { checkChannel(c); checkP2pConfig(config); @@ -1225,7 +1225,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID, @@ -1256,7 +1256,7 @@ public class WifiP2pManager { * @param config the configuration of a p2p group. * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull Channel c, @Nullable WifiP2pConfig config, @Nullable ActionListener listener) { @@ -1344,7 +1344,7 @@ public class WifiP2pManager { * @param servInfo is a local service information. * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) { checkChannel(c); checkServiceInfo(servInfo); @@ -1454,7 +1454,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callbacks on success or failure. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener)); @@ -1530,7 +1530,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callback when peer list is available. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(Channel c, PeerListListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener)); @@ -1553,7 +1553,7 @@ public class WifiP2pManager { * @param c is the channel created at {@link #initialize} * @param listener for callback when group info is available. Can be null. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(Channel c, GroupInfoListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener)); diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index 226c7c48a3b3..c236c7a05488 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -476,4 +476,9 @@ public class BaseWifiService extends IWifiManager.Stub { public void removeWifiUsabilityStatsListener(int listenerIdentifier) { throw new UnsupportedOperationException(); } + + @Override + public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 7bff68aaaa97..449423f44a35 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -60,6 +60,8 @@ public class WifiConfigurationTest { config.setPasspointManagementObjectTree(cookie); config.trusted = false; config.updateIdentifier = "1234"; + config.fromWifiNetworkSpecifier = true; + config.fromWifiNetworkSuggestion = true; MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress(); Parcel parcelW = Parcel.obtain(); config.writeToParcel(parcelW, 0); @@ -76,6 +78,8 @@ public class WifiConfigurationTest { assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress()); assertEquals(config.updateIdentifier, reconfig.updateIdentifier); assertFalse(reconfig.trusted); + assertTrue(config.fromWifiNetworkSpecifier); + assertTrue(config.fromWifiNetworkSuggestion); Parcel parcelWW = Parcel.obtain(); reconfig.writeToParcel(parcelWW, 0); diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java index 677bf371c781..948dcfa47f59 100644 --- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java +++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java @@ -35,6 +35,7 @@ public class WifiInfoTest { private static final long TEST_TX_RETRIES = 2; private static final long TEST_TX_BAD = 3; private static final long TEST_RX_SUCCESS = 4; + private static final String TEST_PACKAGE_NAME = "com.test.example"; /** * Verify parcel write/read with WifiInfo. @@ -48,6 +49,7 @@ public class WifiInfoTest { writeWifiInfo.rxSuccess = TEST_RX_SUCCESS; writeWifiInfo.setTrusted(true); writeWifiInfo.setOsuAp(true); + writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME); Parcel parcel = Parcel.obtain(); writeWifiInfo.writeToParcel(parcel, 0); @@ -62,5 +64,6 @@ public class WifiInfoTest { assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess); assertTrue(readWifiInfo.isTrusted()); assertTrue(readWifiInfo.isOsuAp()); + assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName()); } } |