diff options
217 files changed, 9906 insertions, 3107 deletions
diff --git a/Android.bp b/Android.bp index dd46108b8576..b30bdaa37246 100644 --- a/Android.bp +++ b/Android.bp @@ -74,6 +74,14 @@ filegroup { } filegroup { + name: "framework-identity-sources", + srcs: [ + "identity/java/**/*.java", + ], + path: "identity/java", +} + +filegroup { name: "framework-keystore-sources", srcs: [ "keystore/java/**/*.java", @@ -210,6 +218,7 @@ filegroup { ":framework-graphics-sources", ":framework-jobscheduler-sources", // jobscheduler is not a module for R ":framework-keystore-sources", + ":framework-identity-sources", ":framework-location-sources", ":framework-lowpan-sources", ":framework-mca-effect-sources", @@ -232,6 +241,7 @@ filegroup { ":platform-compat-native-aidl", // AIDL sources from external directories + ":credstore_aidl", ":dumpstate_aidl", ":framework_native_aidl", ":gatekeeper_aidl", @@ -295,6 +305,7 @@ java_defaults { "core/java", "drm/java", "graphics/java", + "identity/java", "keystore/java", "location/java", "lowpan/java", @@ -474,7 +485,6 @@ java_library { "//frameworks/base/apex/permission/framework", "//frameworks/base/apex/statsd/service", "//frameworks/base/telephony", - "//frameworks/base/wifi", "//frameworks/opt/net/wifi/service", ], } @@ -501,8 +511,7 @@ java_library { "framework-sdkextensions-stubs-systemapi", // TODO(b/146167933): Use framework-statsd-stubs instead. "framework-statsd", - // TODO(b/140299412): should be framework-wifi-stubs - "framework-wifi", + "framework-wifi-stubs", "ike-stubs", // TODO(b/147200698): should be the stub of framework-tethering "framework-tethering", @@ -541,7 +550,8 @@ java_library { "compat-changeid-annotation-processor", ], static_libs: [ - "exoplayer2-core" + "exoplayer2-core", + "android.hardware.wifi-V1.0-java-constants", ] } @@ -1206,7 +1216,7 @@ java_library { "core/java/com/android/internal/util/Protocol.java", "core/java/com/android/internal/util/Preconditions.java", "telephony/java/android/telephony/Annotation.java", - ":net-utils-framework-wifi-common-srcs", + ":net-utils-framework-wifi-common-srcs", ], libs: [ "framework-annotations-lib", @@ -1233,6 +1243,7 @@ filegroup { "core/java/com/android/internal/util/StateMachine.java", "core/java/com/android/internal/util/WakeupMessage.java", ], + visibility: ["//frameworks/opt/net/wifi/service"], } // TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java index f32bf9a4b9e6..c62aad622f25 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java @@ -20,6 +20,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import android.app.Activity; import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; import android.perftests.utils.BenchmarkState; @@ -149,7 +150,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, mOutContentInsets, mOutVisibleInsets, mOutStableInsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState); + mOutSurfaceControl, mOutInsetsState, new Point()); } } } diff --git a/api/current.txt b/api/current.txt index 430e8407a4d8..00b8636e26e6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6470,7 +6470,11 @@ package android.app { method public void disableCarMode(int); method public void enableCarMode(int); method public int getCurrentModeType(); + method @NonNull public java.time.LocalTime getCustomNightModeEnd(); + method @NonNull public java.time.LocalTime getCustomNightModeStart(); method public int getNightMode(); + method public void setCustomNightModeEnd(@NonNull java.time.LocalTime); + method public void setCustomNightModeStart(@NonNull java.time.LocalTime); method public void setNightMode(int); field public static String ACTION_ENTER_CAR_MODE; field public static String ACTION_ENTER_DESK_MODE; @@ -6480,6 +6484,7 @@ package android.app { field public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 2; // 0x2 field public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 1; // 0x1 field public static final int MODE_NIGHT_AUTO = 0; // 0x0 + field public static final int MODE_NIGHT_CUSTOM = 3; // 0x3 field public static final int MODE_NIGHT_NO = 1; // 0x1 field public static final int MODE_NIGHT_YES = 2; // 0x2 } @@ -6869,6 +6874,7 @@ package android.app.admin { method public boolean isDeviceOwnerApp(String); method public boolean isEphemeralUser(@NonNull android.content.ComponentName); method public boolean isLockTaskPermitted(String); + method public boolean isLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName); method public boolean isLogoutEnabled(); method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); @@ -6933,6 +6939,7 @@ package android.app.admin { method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean); method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int); method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException; + method public void setLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName, boolean); method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean); method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); method public void setMasterVolumeMuted(@NonNull android.content.ComponentName, boolean); @@ -12032,7 +12039,6 @@ package android.content.pm { field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore"; field public static final String FEATURE_TELEPHONY = "android.hardware.telephony"; field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; - field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; @@ -12253,12 +12259,14 @@ package android.content.pm { field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1 field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8 + field public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40 field public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10 field public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1 field public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8 field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2 field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20 + field public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80 field public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0 field public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4 field public int flags; @@ -16980,7 +16988,9 @@ package android.hardware.biometrics { ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature); ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher); ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac); + ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential); method public javax.crypto.Cipher getCipher(); + method @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); method public javax.crypto.Mac getMac(); method public java.security.Signature getSignature(); } @@ -17941,7 +17951,9 @@ package android.hardware.fingerprint { ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature); ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher); ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac); + ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull android.security.identity.IdentityCredential); method @Deprecated public javax.crypto.Cipher getCipher(); + method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); method @Deprecated public javax.crypto.Mac getMac(); method @Deprecated public java.security.Signature getSignature(); } @@ -25775,10 +25787,12 @@ package android.media { field public static final int COLOR_TRANSFER_LINEAR = 1; // 0x1 field public static final int COLOR_TRANSFER_SDR_VIDEO = 3; // 0x3 field public static final int COLOR_TRANSFER_ST2084 = 6; // 0x6 + field public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode"; field public static final String KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-cut-level"; field public static final String KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-level"; field public static final String KEY_AAC_DRC_EFFECT_TYPE = "aac-drc-effect-type"; field public static final String KEY_AAC_DRC_HEAVY_COMPRESSION = "aac-drc-heavy-compression"; + field public static final String KEY_AAC_DRC_OUTPUT_LOUDNESS = "aac-drc-output-loudness"; field public static final String KEY_AAC_DRC_TARGET_REFERENCE_LEVEL = "aac-target-ref-level"; field public static final String KEY_AAC_ENCODED_TARGET_LEVEL = "aac-encoded-target-level"; field public static final String KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT = "aac-max-output-channel_count"; @@ -36498,10 +36512,10 @@ package android.os { method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle); method @WorkerThread public android.os.Bundle getApplicationRestrictions(String); method public long getSerialNumberForUser(android.os.UserHandle); - method public int getUserCount(); + method @RequiresPermission("android.permission.MANAGE_USERS") public int getUserCount(); method public long getUserCreationTime(android.os.UserHandle); method public android.os.UserHandle getUserForSerialNumber(long); - method public String getUserName(); + method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional=true) public String getUserName(); method public java.util.List<android.os.UserHandle> getUserProfiles(); method public android.os.Bundle getUserRestrictions(); method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); @@ -36511,14 +36525,14 @@ package android.os { method public boolean isQuietModeEnabled(android.os.UserHandle); method public boolean isSystemUser(); method public boolean isUserAGoat(); - method public boolean isUserRunning(android.os.UserHandle); - method public boolean isUserRunningOrStopping(android.os.UserHandle); + method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunning(android.os.UserHandle); + method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle); method public boolean isUserUnlocked(); - method public boolean isUserUnlocked(android.os.UserHandle); - method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserUnlocked(android.os.UserHandle); + method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.MODIFY_QUIET_MODE"}, conditional=true) public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle); method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle, int); method @Deprecated public boolean setRestrictionsChallenge(String); - method @Deprecated public void setUserRestriction(String, boolean); + method @Deprecated @RequiresPermission("android.permission.MANAGE_USERS") public void setUserRestriction(String, boolean); method @Deprecated public void setUserRestrictions(android.os.Bundle); method @Deprecated public void setUserRestrictions(android.os.Bundle, android.os.UserHandle); method public static boolean supportsMultipleUsers(); @@ -42058,6 +42072,139 @@ package android.security { } +package android.security.identity { + + public class AccessControlProfile { + } + + public static final class AccessControlProfile.Builder { + ctor public AccessControlProfile.Builder(@NonNull android.security.identity.AccessControlProfileId); + method @NonNull public android.security.identity.AccessControlProfile build(); + method @NonNull public android.security.identity.AccessControlProfile.Builder setReaderCertificate(@NonNull java.security.cert.X509Certificate); + method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationRequired(boolean); + method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationTimeout(long); + } + + public class AccessControlProfileId { + ctor public AccessControlProfileId(int); + method public int getId(); + } + + public class AlreadyPersonalizedException extends android.security.identity.IdentityCredentialException { + ctor public AlreadyPersonalizedException(@NonNull String); + ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable); + } + + public class CipherSuiteNotSupportedException extends android.security.identity.IdentityCredentialException { + ctor public CipherSuiteNotSupportedException(@NonNull String); + ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable); + } + + public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException { + ctor public DocTypeNotSupportedException(@NonNull String); + ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable); + } + + public class EphemeralPublicKeyNotFoundException extends android.security.identity.IdentityCredentialException { + ctor public EphemeralPublicKeyNotFoundException(@NonNull String); + ctor public EphemeralPublicKeyNotFoundException(@NonNull String, @NonNull Throwable); + } + + public abstract class IdentityCredential { + method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair(); + method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException; + method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]); + method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification(); + method @NonNull public abstract int[] getAuthenticationDataUsageCount(); + method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(); + method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException; + method public abstract void setAllowUsingExhaustedKeys(boolean); + method public abstract void setAvailableAuthenticationKeys(int, int); + method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException; + method public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException; + } + + public class IdentityCredentialException extends java.lang.Exception { + ctor public IdentityCredentialException(@NonNull String); + ctor public IdentityCredentialException(@NonNull String, @NonNull Throwable); + } + + public abstract class IdentityCredentialStore { + method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException; + method @Nullable public abstract byte[] deleteCredentialByName(@NonNull String); + method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException; + method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context); + method @Nullable public static android.security.identity.IdentityCredentialStore getInstance(@NonNull android.content.Context); + method @NonNull public abstract String[] getSupportedDocTypes(); + field public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; // 0x1 + } + + public class InvalidReaderSignatureException extends android.security.identity.IdentityCredentialException { + ctor public InvalidReaderSignatureException(@NonNull String); + ctor public InvalidReaderSignatureException(@NonNull String, @NonNull Throwable); + } + + public class InvalidRequestMessageException extends android.security.identity.IdentityCredentialException { + ctor public InvalidRequestMessageException(@NonNull String); + ctor public InvalidRequestMessageException(@NonNull String, @NonNull Throwable); + } + + public class MessageDecryptionException extends android.security.identity.IdentityCredentialException { + ctor public MessageDecryptionException(@NonNull String); + ctor public MessageDecryptionException(@NonNull String, @NonNull Throwable); + } + + public class NoAuthenticationKeyAvailableException extends android.security.identity.IdentityCredentialException { + ctor public NoAuthenticationKeyAvailableException(@NonNull String); + ctor public NoAuthenticationKeyAvailableException(@NonNull String, @NonNull Throwable); + } + + public class PersonalizationData { + } + + public static final class PersonalizationData.Builder { + ctor public PersonalizationData.Builder(); + method @NonNull public android.security.identity.PersonalizationData.Builder addAccessControlProfile(@NonNull android.security.identity.AccessControlProfile); + method @NonNull public android.security.identity.PersonalizationData build(); + method @NonNull public android.security.identity.PersonalizationData.Builder setEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]); + } + + public abstract class ResultData { + method @NonNull public abstract byte[] getAuthenticatedData(); + method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String); + method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String); + method @Nullable public abstract byte[] getMessageAuthenticationCode(); + method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaceNames(); + method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String); + method @NonNull public abstract byte[] getStaticAuthenticationData(); + method public abstract int getStatus(@NonNull String, @NonNull String); + field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3 + field public static final int STATUS_NOT_REQUESTED = 2; // 0x2 + field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6 + field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1 + field public static final int STATUS_OK = 0; // 0x0 + field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5 + field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4 + } + + public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException { + ctor public SessionTranscriptMismatchException(@NonNull String); + ctor public SessionTranscriptMismatchException(@NonNull String, @NonNull Throwable); + } + + public class UnknownAuthenticationKeyException extends android.security.identity.IdentityCredentialException { + ctor public UnknownAuthenticationKeyException(@NonNull String); + ctor public UnknownAuthenticationKeyException(@NonNull String, @NonNull Throwable); + } + + public abstract class WritableIdentityCredential { + ctor public WritableIdentityCredential(); + method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[]); + method @NonNull public abstract byte[] personalize(@NonNull android.security.identity.PersonalizationData); + } + +} + package android.security.keystore { public class KeyExpiredException extends java.security.InvalidKeyException { @@ -50422,6 +50569,7 @@ package android.util { method public static java.util.TimeZone getTimeZone(int, boolean, long, String); method public static String getTimeZoneDatabaseVersion(); method @Nullable public static java.util.List<java.lang.String> getTimeZoneIdsForCountryCode(@NonNull String); + method public static boolean isTimeBetween(@NonNull java.time.LocalTime, @NonNull java.time.LocalTime, @NonNull java.time.LocalTime); } @Deprecated public class TimingLogger { @@ -53886,9 +54034,9 @@ package android.view { public final class WindowInsets { ctor public WindowInsets(android.view.WindowInsets); - method @NonNull public android.view.WindowInsets consumeDisplayCutout(); - method @NonNull public android.view.WindowInsets consumeStableInsets(); - method @NonNull public android.view.WindowInsets consumeSystemWindowInsets(); + method @Deprecated @NonNull public android.view.WindowInsets consumeDisplayCutout(); + method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets(); + method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets(); method @Nullable public android.view.DisplayCutout getDisplayCutout(); method @NonNull public android.graphics.Insets getInsets(int); method @NonNull public android.graphics.Insets getMandatorySystemGestureInsets(); @@ -53914,6 +54062,7 @@ package android.view { method public boolean isVisible(int); method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int); method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(android.graphics.Rect); + field @NonNull public static final android.view.WindowInsets CONSUMED; } public static final class WindowInsets.Builder { @@ -53945,10 +54094,13 @@ package android.view { } public interface WindowInsetsAnimationCallback { + method public int getDispatchMode(); method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets); method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + field public static final int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; // 0x1 + field public static final int DISPATCH_MODE_STOP = 0; // 0x0 } public static final class WindowInsetsAnimationCallback.AnimationBounds { @@ -53988,8 +54140,10 @@ package android.view { public interface WindowInsetsController { method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener); + method public int getSystemBarsAppearance(); + method public int getSystemBarsBehavior(); method public default void hideInputMethod(); - method public void setSystemBarsAppearance(int); + method public void setSystemBarsAppearance(int, int); method public void setSystemBarsBehavior(int); method public default void showInputMethod(); field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10 @@ -59690,7 +59844,7 @@ package android.widget { method public int getGravity(); method public float getHorizontalMargin(); method public float getVerticalMargin(); - method public android.view.View getView(); + method @Deprecated public android.view.View getView(); method public int getXOffset(); method public int getYOffset(); method public static android.widget.Toast makeText(android.content.Context, CharSequence, int); @@ -59701,7 +59855,7 @@ package android.widget { method public void setMargin(float, float); method public void setText(@StringRes int); method public void setText(CharSequence); - method public void setView(android.view.View); + method @Deprecated public void setView(android.view.View); method public void show(); field public static final int LENGTH_LONG = 1; // 0x1 field public static final int LENGTH_SHORT = 0; // 0x0 diff --git a/api/system-current.txt b/api/system-current.txt index d1696c8256c4..f95f715110a4 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -67,6 +67,7 @@ package android { field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"; field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN"; + field public static final String CREATE_USERS = "android.permission.CREATE_USERS"; field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; @@ -327,13 +328,15 @@ package android.app { method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String); method @NonNull public java.util.Collection<java.util.Locale> getSupportedLocales(); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, "android.permission.CREATE_USERS"}) public boolean isProfileForeground(@NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isProfileForeground(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String); method @RequiresPermission(android.Manifest.permission.KILL_UID) public void killUid(int, String); + method public void registerHomeVisibilityObserver(@NonNull android.app.HomeVisibilityObserver); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener); method public void setDeviceLocales(@NonNull android.os.LocaleList); method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle); + method public void unregisterHomeVisibilityObserver(@NonNull android.app.HomeVisibilityObserver); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); } @@ -586,6 +589,11 @@ package android.app { field public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED"; } + public abstract class HomeVisibilityObserver { + ctor public HomeVisibilityObserver(); + method public abstract void onHomeVisibilityChanged(boolean); + } + public abstract class InstantAppResolverService extends android.app.Service { ctor public InstantAppResolverService(); method public final void attachBaseContext(android.content.Context); @@ -2120,6 +2128,10 @@ package android.content.pm { public class PackageInstaller { method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); + field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2 + field public static final int DATA_LOADER_TYPE_NONE = 0; // 0x0 + field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1 + field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE"; } public static class PackageInstaller.Session implements java.io.Closeable { @@ -4724,7 +4736,9 @@ package android.media.tv.tuner { method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnb(@Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.LnbCallback); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnbByName(@Nullable String, @Nullable java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback); method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter(); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings); } @@ -4740,6 +4754,9 @@ package android.media.tv.tuner { field public static final int RESULT_SUCCESS = 0; // 0x0 field public static final int RESULT_UNAVAILABLE = 1; // 0x1 field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6 + field public static final int SCAN_TYPE_AUTO = 1; // 0x1 + field public static final int SCAN_TYPE_BLIND = 2; // 0x2 + field public static final int SCAN_TYPE_UNDEFINED = 0; // 0x0 field public static final int SC_HEVC_INDEX_AUD = 2; // 0x2 field public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP = 16; // 0x10 field public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL = 8; // 0x8 @@ -5166,6 +5183,11 @@ package android.media.tv.tuner.frontend { method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setSifStandard(int); } + public class Atsc3PlpInfo { + method public boolean getLlsFlag(); + method public int getPlpId(); + } + public abstract class FrontendSettings { method public int getFrequency(); method public abstract int getType(); @@ -5192,6 +5214,23 @@ package android.media.tv.tuner.frontend { field public static final int SIGNAL_NO_SIGNAL = 1; // 0x1 } + public interface ScanCallback { + method public void onAnalogSifStandard(int); + method public void onAtsc3PlpInfos(@NonNull android.media.tv.tuner.frontend.Atsc3PlpInfo[]); + method public void onDvbsStandard(int); + method public void onDvbtStandard(int); + method public void onFrequenciesReport(@NonNull int[]); + method public void onGroupIds(@NonNull int[]); + method public void onHierarchy(int); + method public void onInputStreamIds(@NonNull int[]); + method public void onLocked(); + method public void onPlpIds(@NonNull int[]); + method public void onProgress(@IntRange(from=0, to=100) int); + method public void onScanStopped(); + method public void onSignalType(int); + method public void onSymbolRates(@NonNull int[]); + } + } package android.metrics { @@ -5457,8 +5496,11 @@ package android.net { method public int getLegacyType(); method @NonNull public String getLegacyTypeName(); method @Nullable public String getSubscriberId(); + method public boolean isExplicitlySelected(); method public boolean isNat64DetectionEnabled(); + method public boolean isPartialConnectivityAcceptable(); method public boolean isProvisioningNotificationEnabled(); + method public boolean isUnvalidatedConnectivityAcceptable(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; } @@ -5468,9 +5510,12 @@ package android.net { method @NonNull public android.net.NetworkAgentConfig build(); method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection(); method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification(); + method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); + method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); } public final class NetworkCapabilities implements android.os.Parcelable { @@ -6840,6 +6885,11 @@ package android.net.wifi { method public boolean satisfiedBy(android.net.NetworkSpecifier); } + public final class WifiNetworkSuggestion implements android.os.Parcelable { + method @Nullable public android.net.wifi.hotspot2.PasspointConfiguration getPasspointConfiguration(); + method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration(); + } + public static final class WifiNetworkSuggestion.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int); } @@ -6867,17 +6917,17 @@ package android.net.wifi { method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]); method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings); method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<java.lang.Integer> getAvailableChannels(int); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults(); + method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults(); method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<android.net.wifi.ScanResult> getSingleScanResults(); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void registerScanListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiScanner.ScanListener); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setScanningEnabled(boolean); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource); + method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource); method @Deprecated public void startTrackingBssids(android.net.wifi.WifiScanner.BssidInfo[], int, android.net.wifi.WifiScanner.BssidListener); method @Deprecated public void startTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener); + method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopScan(android.net.wifi.WifiScanner.ScanListener); method @Deprecated public void stopTrackingBssids(android.net.wifi.WifiScanner.BssidListener); method @Deprecated public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener); @@ -6960,7 +7010,7 @@ package android.net.wifi { public static interface WifiScanner.ScanListener extends android.net.wifi.WifiScanner.ActionListener { method public void onFullResult(android.net.wifi.ScanResult); - method public void onPeriodChanged(int); + method @Deprecated public void onPeriodChanged(int); method public void onResults(android.net.wifi.WifiScanner.ScanData[]); } @@ -6971,12 +7021,12 @@ package android.net.wifi { field @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public final java.util.List<android.net.wifi.WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks; field public boolean hideFromAppOps; field public boolean ignoreLocationSettings; - field public int maxPeriodInMs; - field public int maxScansToCache; - field public int numBssidsPerScan; - field public int periodInMs; - field public int reportEvents; - field public int stepCount; + field @Deprecated public int maxPeriodInMs; + field @Deprecated public int maxScansToCache; + field @Deprecated public int numBssidsPerScan; + field @Deprecated public int periodInMs; + field @Deprecated public int reportEvents; + field @Deprecated public int stepCount; field @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public int type; } @@ -7973,6 +8023,7 @@ package android.os { public class UserManager { method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearSeedAccountData(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @Nullable String[]) throws android.os.UserManager.UserOperationException; method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions(); @@ -7980,22 +8031,26 @@ package android.os { method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public long[] getSerialNumbersOfUsers(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserHandle> getUserHandles(boolean); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getUserProfiles(boolean); method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle); method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability(); - method public boolean hasRestrictedProfiles(); + method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isProfile(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(@NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isRestrictedProfile(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet(); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean removeUser(@NonNull android.os.UserHandle); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap); + method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull String); + method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull android.os.UserHandle, @NonNull String); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String); field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED"; field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; @@ -8008,6 +8063,10 @@ package android.os { field public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 4; // 0x4 field public static final int SWITCHABILITY_STATUS_USER_IN_CALL = 1; // 0x1 field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2 + field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; + field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; + field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; + field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS"; } public static final class UserManager.EnforcingUser implements android.os.Parcelable { @@ -9277,6 +9336,7 @@ package android.service.euicc { public abstract class EuiccService extends android.app.Service { ctor public EuiccService(); + method public void dump(@NonNull java.io.PrintWriter); method @CallSuper public android.os.IBinder onBind(android.content.Intent); method public abstract int onDeleteSubscription(int, String); method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle); diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt index da0aae0f14ad..fde6bb3424f7 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -1,33 +1,48 @@ // Baseline format: 1.0 +AcronymName: android.net.NetworkCapabilities#setSSID(String): + Acronyms should not be capitalized in method names: was `setSSID`, should this be `setSsid`? + + ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION: ActionValue: android.net.wifi.WifiManager#ACTION_LINK_CONFIGURATION_CHANGED: - Inconsistent action value; expected `android.net.wifi.action.LINK_CONFIGURATION_CHANGED`, was `android.net.wifi.LINK_CONFIGURATION_CHANGED` + +ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #1: + Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]` +ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #2: + Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]` +ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsLocalCapabilities(): + Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]` +ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsSelectableCapabilities(): + Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]` +ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #0: + Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]` +ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #1: + Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]` +ArrayReturn: android.media.tv.tuner.Tuner.FilterCallback#onFilterEvent(android.media.tv.tuner.Tuner.Filter, android.media.tv.tuner.filter.FilterEvent[]) parameter #1: + Method parameter should be Collection<FilterEvent> (or subclass) instead of raw array; was `android.media.tv.tuner.filter.FilterEvent[]` +ArrayReturn: android.net.NetworkScoreManager#requestScores(android.net.NetworkKey[]) parameter #0: + Method parameter should be Collection<NetworkKey> (or subclass) instead of raw array; was `android.net.NetworkKey[]` ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions(): ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#deletePersistentGroup(android.net.wifi.p2p.WifiP2pManager.Channel, int, android.net.wifi.p2p.WifiP2pManager.ActionListener): - Registration methods should have overload that accepts delivery Executor: `deletePersistentGroup` + ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#factoryReset(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener): - Registration methods should have overload that accepts delivery Executor: `factoryReset` + ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#listen(android.net.wifi.p2p.WifiP2pManager.Channel, boolean, android.net.wifi.p2p.WifiP2pManager.ActionListener): - Registration methods should have overload that accepts delivery Executor: `listen` + ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#requestPersistentGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener): - Registration methods should have overload that accepts delivery Executor: `requestPersistentGroupInfo` + ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setDeviceName(android.net.wifi.p2p.WifiP2pManager.Channel, String, android.net.wifi.p2p.WifiP2pManager.ActionListener): - Registration methods should have overload that accepts delivery Executor: `setDeviceName` + ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setWfdInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pWfdInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener): - Registration methods should have overload that accepts delivery Executor: `setWfdInfo` + ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setWifiP2pChannels(android.net.wifi.p2p.WifiP2pManager.Channel, int, int, android.net.wifi.p2p.WifiP2pManager.ActionListener): - Registration methods should have overload that accepts delivery Executor: `setWifiP2pChannels` - -HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities(): - Type must not be heavy BitSet (method android.net.wifi.wificond.NativeScanResult.getCapabilities()) -PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback): - Found registerApCallback but not unregisterApCallback in android.net.wifi.wificond.WifiCondManager + GenericException: android.app.prediction.AppPredictor#finalize(): @@ -40,13 +55,22 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize(): +HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities(): + + +IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler): + Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers` KotlinKeyword: android.app.Notification#when: +KotlinOperator: android.telephony.CbGeoUtils.Geometry#contains(android.telephony.CbGeoUtils.LatLng): + Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object) + + MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString(): MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#writeToParcel(android.os.Parcel, int) parameter #0: @@ -70,7 +94,7 @@ MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1: MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2: - + MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig): MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0: @@ -157,43 +181,60 @@ MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId( - MutableBareField: android.net.IpConfiguration#httpProxy: - Bare field httpProxy must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.IpConfiguration#ipAssignment: - Bare field ipAssignment must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.IpConfiguration#proxySettings: - Bare field proxySettings must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.IpConfiguration#staticIpConfiguration: - Bare field staticIpConfiguration must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#allowAutojoin: MutableBareField: android.net.wifi.WifiConfiguration#apBand: - Bare field apBand must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#carrierId: MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSpecifier: - Bare field fromWifiNetworkSpecifier must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSuggestion: - Bare field fromWifiNetworkSuggestion must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#macRandomizationSetting: - Bare field macRandomizationSetting must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#meteredOverride: - Bare field meteredOverride must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#requirePMF: - Bare field requirePMF must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#saePasswordId: - Bare field saePasswordId must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiConfiguration#shared: - Bare field shared must be marked final, or moved behind accessors if mutable + MutableBareField: android.net.wifi.WifiScanner.ScanSettings#type: - Bare field type must be marked final, or moved behind accessors if mutable + NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0: +NotCloseable: android.bluetooth.BluetoothA2dpSink: + Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothA2dpSink +NotCloseable: android.bluetooth.BluetoothMap: + Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothMap +NotCloseable: android.bluetooth.BluetoothPan: + Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPan +NotCloseable: android.bluetooth.BluetoothPbap: + Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPbap + + +OnNameExpected: android.content.ContentProvider#checkUriPermission(android.net.Uri, int, int): + If implemented by developer, should follow the on<Something> style; otherwise consider marking final + + +PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback): + + + ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context): ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]): @@ -201,6 +242,7 @@ ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java. ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context): + SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean): @@ -246,9 +288,11 @@ SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.ap SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler): SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String): - + +SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler): + SAM-compatible parameters (such as parameter 1, "listener", in android.app.WallpaperManager.addOnColorsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback): - + SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection): SamShouldBeLast: android.content.Context#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection): @@ -279,12 +323,20 @@ SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageC SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback): +SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener): SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener): +SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener): +SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper): + SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler): SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int): @@ -380,7 +432,7 @@ SamShouldBeLast: android.telephony.TelephonyManager#requestNetworkScan(android.t SamShouldBeLast: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>): SamShouldBeLast: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>): - + SamShouldBeLast: android.view.View#postDelayed(Runnable, long): SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long): @@ -445,3 +497,11 @@ ServiceName: android.Manifest.permission#REQUEST_NOTIFICATION_ASSISTANT_SERVICE: ServiceName: android.provider.DeviceConfig#NAMESPACE_PACKAGE_MANAGER_SERVICE: + + +UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle): + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added + + +UserHandleName: android.telephony.CellBroadcastIntents#sendOrderedBroadcastForBackgroundReceivers(android.content.Context, android.os.UserHandle, android.content.Intent, String, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle): + Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `sendOrderedBroadcastForBackgroundReceivers` diff --git a/api/test-current.txt b/api/test-current.txt index b1003d61495e..d4b799dd405e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -75,8 +75,10 @@ package android.app { method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle); - field public static final int PROCESS_CAPABILITY_ALL = 1; // 0x1 + field public static final int PROCESS_CAPABILITY_ALL = 7; // 0x7 + field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 + field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 } @@ -4456,7 +4458,7 @@ package android.view { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40 field public CharSequence accessibilityTitle; - field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="FIT_INSETS_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="ONLY_DRAW_BOTTOM_BAR_BACKGROUND")}) public int privateFlags; + field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="APPEARANCE_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="BEHAVIOR_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x10000000, equals=0x10000000, name="FIT_INSETS_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x20000000, equals=0x20000000, name="ONLY_DRAW_BOTTOM_BAR_BACKGROUND")}) public int privateFlags; } } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 4372e2245ee8..468a43fe84f9 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -95,7 +95,7 @@ message Atom { ExcessiveCpuUsageReported excessive_cpu_usage_reported = 16; CachedKillReported cached_kill_reported = 17; ProcessMemoryStatReported process_memory_stat_reported = 18; - LauncherUIChanged launcher_event = 19; + LauncherUIChanged launcher_event = 19 [(module) = "sysui"]; BatterySaverModeStateChanged battery_saver_mode_state_changed = 20; DeviceIdleModeStateChanged device_idle_mode_state_changed = 21; DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22; @@ -138,9 +138,9 @@ message Atom { OverlayStateChanged overlay_state_changed = 59; ForegroundServiceStateChanged foreground_service_state_changed = 60; CallStateChanged call_state_changed = 61; - KeyguardStateChanged keyguard_state_changed = 62; - KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63; - KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64; + KeyguardStateChanged keyguard_state_changed = 62 [(module) = "sysui"]; + KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63 [(module) = "sysui"]; + KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64 [(module) = "sysui"]; AppDied app_died = 65; ResourceConfigurationChanged resource_configuration_changed = 66; BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67; @@ -231,7 +231,7 @@ message Atom { UsbContaminantReported usb_contaminant_reported = 146; WatchdogRollbackOccurred watchdog_rollback_occurred = 147; BiometricSystemHealthIssueDetected biometric_system_health_issue_detected = 148; - BubbleUIChanged bubble_ui_changed = 149; + BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"]; ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150; BluetoothActiveDeviceChanged bluetooth_active_device_changed = 151; BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = 152; @@ -262,7 +262,7 @@ message Atom { AssistGestureProgressReported assist_gesture_progress_reported = 176; TouchGestureClassified touch_gesture_classified = 177; HiddenApiUsed hidden_api_used = 178 [(allow_from_any_uid) = true]; - StyleUIChanged style_ui_changed = 179 [(module) = "style"]; + StyleUIChanged style_ui_changed = 179 [(module) = "sysui"]; PrivacyIndicatorsInteracted privacy_indicators_interacted = 180 [(module) = "permissioncontroller"]; AppInstallOnExternalStorageReported app_install_on_external_storage_reported = 181; @@ -317,7 +317,7 @@ message Atom { ConversationActionsEvent conversation_actions_event = 221 [(module) = "textclassifier"]; LanguageDetectionEvent language_detection_event = 222 [(module) = "textclassifier"]; ExclusionRectStateChanged exclusion_rect_state_changed = 223; - BackGesture back_gesture_reported_reported = 224; + BackGesture back_gesture_reported_reported = 224 [(module) = "sysui"]; UpdateEngineUpdateAttemptReported update_engine_update_attempt_reported = 225; UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226; CameraActionEvent camera_action_event = 227; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 67a59f1975d5..f7c4d96d0d40 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -80,6 +80,7 @@ import com.android.internal.os.RoSystemProperties; import com.android.internal.os.TransferPipe; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.MemInfoReader; +import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import org.xmlpull.v1.XmlSerializer; @@ -573,6 +574,8 @@ public class ActivityManager { @IntDef(flag = true, prefix = { "PROCESS_CAPABILITY_" }, value = { PROCESS_CAPABILITY_NONE, PROCESS_CAPABILITY_FOREGROUND_LOCATION, + PROCESS_CAPABILITY_FOREGROUND_CAMERA, + PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, }) @Retention(RetentionPolicy.SOURCE) public @interface ProcessCapability {} @@ -585,9 +588,19 @@ public class ActivityManager { @TestApi public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 0; + /** @hide Process can access camera while in foreground */ + @TestApi + public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 1; + + /** @hide Process can access microphone while in foreground */ + @TestApi + public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; + /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/ @TestApi - public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION; + public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION + | PROCESS_CAPABILITY_FOREGROUND_CAMERA + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; // NOTE: If PROCESS_STATEs are added, then new fields must be added // to frameworks/base/core/proto/android/app/enums.proto and the following method must @@ -4677,4 +4690,35 @@ public class ActivityManager { throw e.rethrowFromSystemServer(); } } + + /** + * Register with {@link HomeVisibilityObserver} with ActivityManager. + * @hide + */ + @SystemApi + public void registerHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) { + Preconditions.checkNotNull(observer); + try { + observer.init(mContext, this); + getService().registerProcessObserver(observer.mObserver); + // Notify upon first registration. + observer.onHomeVisibilityChanged(observer.mIsHomeActivityVisible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregister with {@link HomeVisibilityObserver} with ActivityManager. + * @hide + */ + @SystemApi + public void unregisterHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) { + Preconditions.checkNotNull(observer); + try { + getService().unregisterProcessObserver(observer.mObserver); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 326a0761dff3..fd56834ac361 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -618,8 +618,7 @@ public class ApplicationPackageManager extends PackageManager { return hasSystemFeature(name, 0); } - @Override - public boolean hasSystemFeature(String name, int version) { + private boolean hasSystemFeatureUncached(String name, int version) { try { return mPM.hasSystemFeature(name, version); } catch (RemoteException e) { @@ -627,6 +626,64 @@ public class ApplicationPackageManager extends PackageManager { } } + // Make this cache relatively large. There are many system features and + // none are ever invalidated. MPTS tests suggests that the cache should + // hold at least 150 entries. + private static final int SYS_FEATURE_CACHE_SIZE = 256; + private static final String CACHE_KEY_SYS_FEATURE_PROPERTY = "cache_key.has_system_feature"; + + private class SystemFeatureQuery { + public final String name; + public final int version; + public SystemFeatureQuery(String n, int v) { + name = n; + version = v; + } + @Override + public String toString() { + return String.format("SystemFeatureQuery(name=\"%s\", version=%d)", + name, version); + } + @Override + public boolean equals(Object o) { + if (o instanceof SystemFeatureQuery) { + SystemFeatureQuery r = (SystemFeatureQuery) o; + return Objects.equals(name, r.name) && version == r.version; + } else { + return false; + } + } + @Override + public int hashCode() { + return Objects.hashCode(name) + version; + } + } + + private final PropertyInvalidatedCache<SystemFeatureQuery, Boolean> mSysFeatureCache = + new PropertyInvalidatedCache<SystemFeatureQuery, Boolean>( + SYS_FEATURE_CACHE_SIZE, + CACHE_KEY_SYS_FEATURE_PROPERTY) { + @Override + protected Boolean recompute(SystemFeatureQuery query) { + return hasSystemFeatureUncached(query.name, query.version); + } + }; + + @Override + public boolean hasSystemFeature(String name, int version) { + return mSysFeatureCache.query(new SystemFeatureQuery(name, version)).booleanValue(); + } + + /** @hide */ + public void disableSysFeatureCache() { + mSysFeatureCache.disableLocal(); + } + + /** @hide */ + public static void invalidateSysFeatureCache() { + PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SYS_FEATURE_PROPERTY); + } + @Override public int checkPermission(String permName, String pkgName) { try { diff --git a/core/java/android/app/HomeVisibilityObserver.java b/core/java/android/app/HomeVisibilityObserver.java new file mode 100644 index 000000000000..f3465f839b3b --- /dev/null +++ b/core/java/android/app/HomeVisibilityObserver.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.SystemApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; + +import java.util.List; + +/** + * An observer / callback to create and register by + * {@link ActivityManager#registerHomeVisibilityObserver} so that it's triggered when + * visibility of home page changes. + * @hide + */ +@SystemApi +public abstract class HomeVisibilityObserver { + private Context mContext; + private ActivityManager mActivityManager; + /** @hide */ + IProcessObserver.Stub mObserver; + /** @hide */ + boolean mIsHomeActivityVisible; + + /** @hide */ + void init(Context context, ActivityManager activityManager) { + mContext = context; + mActivityManager = activityManager; + mIsHomeActivityVisible = isHomeActivityVisible(); + } + + /** + * The API that needs implemented and will be triggered when activity on home page changes. + */ + public abstract void onHomeVisibilityChanged(boolean isHomeActivityVisible); + + public HomeVisibilityObserver() { + mObserver = new IProcessObserver.Stub() { + @Override + public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) { + boolean isHomeActivityVisible = isHomeActivityVisible(); + if (mIsHomeActivityVisible != isHomeActivityVisible) { + mIsHomeActivityVisible = isHomeActivityVisible; + onHomeVisibilityChanged(mIsHomeActivityVisible); + } + } + + @Override + public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) { + } + + @Override + public void onProcessDied(int pid, int uid) { + } + }; + } + + private boolean isHomeActivityVisible() { + List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1); + if (tasks == null || tasks.isEmpty()) { + return false; + } + + String top = tasks.get(0).topActivity.getPackageName(); + if (top == null) { + return false; + } + + // We can assume that the screen is idle if the home application is in the foreground. + final Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_HOME); + + ResolveInfo info = mContext.getPackageManager().resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY); + if (info != null && top.equals(info.activityInfo.packageName)) { + return true; + } + + return false; + } +} diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index f6014e5fdf80..d665f336ec1d 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -46,12 +46,10 @@ interface INotificationManager void clearData(String pkg, int uid, boolean fromApp); // TODO: Replace parameter (ITransientNotification callback) with (CharSequence text) - void enqueueTextToast(String pkg, ITransientNotification callback, int duration, int displayId); - @UnsupportedAppUsage - void enqueueToast(String pkg, ITransientNotification callback, int duration, int displayId); - @UnsupportedAppUsage - void cancelToast(String pkg, ITransientNotification callback); - void finishToken(String pkg, ITransientNotification callback); + void enqueueTextToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); + void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); + void cancelToast(String pkg, IBinder token); + void finishToken(String pkg, IBinder token); void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, in Notification notification, int userId); diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index f5809ba627ff..41e2ec9e3572 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -70,7 +70,27 @@ interface IUiModeManager { boolean isNightModeLocked(); /** - * @hide + * [De]Activates night mode */ boolean setNightModeActivated(boolean active); + + /** + * Returns custom start clock time + */ + long getCustomNightModeStart(); + + /** + * Sets custom start clock time + */ + void setCustomNightModeStart(long time); + + /** + * Returns custom end clock time + */ + long getCustomNightModeEnd(); + + /** + * Sets custom end clock time + */ + void setCustomNightModeEnd(long time); } diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java index 8ba39a883555..909a476f3c94 100644 --- a/core/java/android/app/NotificationHistory.java +++ b/core/java/android/app/NotificationHistory.java @@ -24,6 +24,8 @@ import android.os.Parcelable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -311,11 +313,23 @@ public final class NotificationHistory implements Parcelable { mHistoryCount++; } + /** + * Used when populating a history from disk; adds an historical notification. + */ + public void addNewNotificationToWrite(@NonNull HistoricalNotification notification) { + if (notification == null) { + return; + } + mNotificationsToWrite.add(0, notification); + mHistoryCount++; + } + public void addNotificationsToWrite(@NonNull NotificationHistory notificationHistory) { for (HistoricalNotification hn : notificationHistory.getNotificationsToWrite()) { - // TODO: consider merging by date addNotificationToWrite(hn); } + Collections.sort(mNotificationsToWrite, + (o1, o2) -> -1 * Long.compare(o1.getPostedTimeMs(), o2.getPostedTimeMs())); poolStringsFromNotifications(); } diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 363306483409..24873b86e32b 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -18,6 +18,7 @@ package android.app; import android.annotation.IntDef; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -32,6 +33,7 @@ import android.os.ServiceManager.ServiceNotFoundException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.LocalTime; /** * This class provides access to the system uimode services. These services @@ -163,6 +165,7 @@ public class UiModeManager { /** @hide */ @IntDef(prefix = { "MODE_" }, value = { MODE_NIGHT_AUTO, + MODE_NIGHT_CUSTOM, MODE_NIGHT_NO, MODE_NIGHT_YES }) @@ -173,19 +176,25 @@ public class UiModeManager { * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: * automatically switch night mode on and off based on the time. */ - public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_UNDEFINED >> 4; + public static final int MODE_NIGHT_AUTO = 0; + + /** + * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: + * automatically switch night mode on and off based on the time. + */ + public static final int MODE_NIGHT_CUSTOM = 3; /** * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: * never run in night mode. */ - public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4; + public static final int MODE_NIGHT_NO = 1; /** * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: * always run in night mode. */ - public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4; + public static final int MODE_NIGHT_YES = 2; private IUiModeManager mService; @@ -377,6 +386,8 @@ public class UiModeManager { * {@code notnight} mode</li> * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into * {@code night} mode</li> + * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between + * {@code night} and {@code notnight} based on the custom time set (or default)</li> * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between * {@code night} and {@code notnight} based on the device's current * location and certain other sensors</li> @@ -418,6 +429,7 @@ public class UiModeManager { * <li>{@link #MODE_NIGHT_NO}</li> * <li>{@link #MODE_NIGHT_YES}</li> * <li>{@link #MODE_NIGHT_AUTO}</li> + * <li>{@link #MODE_NIGHT_CUSTOM}</li> * <li>{@code -1} on error</li> * </ul> * @@ -475,7 +487,7 @@ public class UiModeManager { } /** - * @hide* + * @hide */ public boolean setNightModeActivated(boolean active) { if (mService != null) { @@ -487,4 +499,75 @@ public class UiModeManager { } return false; } + + /** + * Returns the time of the day Dark theme activates + * <p> + * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses + * this time set to activate it automatically. + */ + @NonNull + public LocalTime getCustomNightModeStart() { + if (mService != null) { + try { + return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return LocalTime.MIDNIGHT; + } + + /** + * Sets the time of the day Dark theme activates + * <p> + * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses + * this time set to activate it automatically + * @param time The time of the day Dark theme should activate + */ + public void setCustomNightModeStart(@NonNull LocalTime time) { + if (mService != null) { + try { + mService.setCustomNightModeStart(time.toNanoOfDay() / 1000); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the time of the day Dark theme deactivates + * <p> + * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses + * this time set to deactivate it automatically. + */ + @NonNull + public LocalTime getCustomNightModeEnd() { + if (mService != null) { + try { + return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return LocalTime.MIDNIGHT; + } + + /** + * Sets the time of the day Dark theme deactivates + * <p> + * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses + * this time set to deactivate it automatically. + * @param time The time of the day Dark theme should deactivate + */ + public void setCustomNightModeEnd(@NonNull LocalTime time) { + if (mService != null) { + try { + mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 56fb50dad5ef..a35a89948f25 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8605,6 +8605,55 @@ public class DevicePolicyManager { } /** + * Called by a device owner or a profile owner of an organization-owned managed profile to + * control whether the user can change networks configured by the admin. + * <p> + * WiFi network configuration lockdown is controlled by a global settings + * {@link android.provider.Settings.Global#WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN} and calling + * this API effectively modifies the global settings. Previously device owners can also + * control this directly via {@link #setGlobalSetting} but they are recommended to switch + * to this API. + * + * @param admin admin Which {@link DeviceAdminReceiver} this request is associated + * with. + * @param lockdown Whether the admin configured networks should be unmodifiable by the + * user. + * @throws SecurityException if caller is not a device owner or a profile owner of an + * organization-owned managed profile. + */ + public void setLockdownAdminConfiguredNetworks(@NonNull ComponentName admin, boolean lockdown) { + throwIfParentInstance("setLockdownAdminConfiguredNetworks"); + if (mService != null) { + try { + mService.setLockdownAdminConfiguredNetworks(admin, lockdown); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Called by a device owner or a profile owner of an organization-owned managed profile to + * determine whether the user is prevented from modifying networks configured by the admin. + * + * @param admin admin Which {@link DeviceAdminReceiver} this request is associated + * with. + * @throws SecurityException if caller is not a device owner or a profile owner of an + * organization-owned managed profile. + */ + public boolean isLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) { + throwIfParentInstance("setLockdownAdminConfiguredNetworks"); + if (mService != null) { + try { + return mService.isLockdownAdminConfiguredNetworks(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Called by a device owner or a profile owner of an organization-owned managed * profile to set the system wall clock time. This only takes effect if called when * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index f649286206bb..a2c0856717f5 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -263,6 +263,9 @@ interface IDevicePolicyManager { void setSystemSetting(in ComponentName who, in String setting, in String value); void setSecureSetting(in ComponentName who, in String setting, in String value); + void setLockdownAdminConfiguredNetworks(in ComponentName who, boolean lockdown); + boolean isLockdownAdminConfiguredNetworks(in ComponentName who); + void setLocationEnabled(in ComponentName who, boolean locationEnabled); boolean setTime(in ComponentName who, long millis); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 3c6f602e93e1..f264adbbb592 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -222,6 +222,19 @@ public class PackageInstaller { public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK"; /** + * Type of DataLoader for this session. Will be one of + * {@link #DATA_LOADER_TYPE_NONE}, {@link #DATA_LOADER_TYPE_STREAMING}, + * {@link #DATA_LOADER_TYPE_INCREMENTAL}. + * <p> + * See the individual types documentation for details. + * + * @see Intent#getIntExtra(String, int) + * {@hide} + */ + @SystemApi + public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE"; + + /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. * @@ -325,6 +338,33 @@ public class PackageInstaller { */ public static final int STATUS_FAILURE_INCOMPATIBLE = 7; + /** + * Default value, non-streaming installation session. + * + * @see #EXTRA_DATA_LOADER_TYPE + * {@hide} + */ + @SystemApi + public static final int DATA_LOADER_TYPE_NONE = DataLoaderType.NONE; + + /** + * Streaming installation using data loader. + * + * @see #EXTRA_DATA_LOADER_TYPE + * {@hide} + */ + @SystemApi + public static final int DATA_LOADER_TYPE_STREAMING = DataLoaderType.STREAMING; + + /** + * Streaming installation using Incremental FileSystem. + * + * @see #EXTRA_DATA_LOADER_TYPE + * {@hide} + */ + @SystemApi + public static final int DATA_LOADER_TYPE_INCREMENTAL = DataLoaderType.INCREMENTAL; + private final IPackageInstaller mInstaller; private final int mUserId; private final String mInstallerPackageName; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 1f502a101290..925d70cdc855 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2323,13 +2323,6 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; /** - * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device - * has a telephony radio that support data. - */ - @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; - - /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device supports telephony carrier restriction mechanism. * diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 00507e1548a6..5f90b6c43c60 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -147,6 +147,28 @@ public class ServiceInfo extends ComponentInfo public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 1 << 5; /** + * Constant corresponding to {@code camera} in + * the {@link android.R.attr#foregroundServiceType} attribute. + * Use the camera device or record video. + * For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} and + * above, a foreground service will not be able to access the camera if this type is not + * specified in the manifest and in + * {@link android.app.Service#startForeground(int, android.app.Notification, int)}. + */ + public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 1 << 6; + + /** + * Constant corresponding to {@code microphone} in + * the {@link android.R.attr#foregroundServiceType} attribute. + * Use the microphone device or record audio. + * For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} and + * above, a foreground service will not be able to access the microphone if this type is not + * specified in the manifest and in + * {@link android.app.Service#startForeground(int, android.app.Notification, int)}. + */ + public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7; + + /** * A special value indicates to use all types set in manifest file. */ public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; @@ -166,6 +188,8 @@ public class ServiceInfo extends ComponentInfo FOREGROUND_SERVICE_TYPE_LOCATION, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE, FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION, + FOREGROUND_SERVICE_TYPE_CAMERA, + FOREGROUND_SERVICE_TYPE_MICROPHONE }) @Retention(RetentionPolicy.SOURCE) public @interface ForegroundServiceType {} diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index a695ce8e511f..c686624fb33b 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -36,6 +36,7 @@ import android.os.CancellationSignal; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.security.identity.IdentityCredential; import android.text.TextUtils; import android.util.Log; @@ -555,6 +556,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan super(mac); } + public CryptoObject(@NonNull IdentityCredential credential) { + super(credential); + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -578,6 +583,14 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public Mac getMac() { return super.getMac(); } + + /** + * Get {@link IdentityCredential} object. + * @return {@link IdentityCredential} object or null if this doesn't contain one. + */ + public @Nullable IdentityCredential getIdentityCredential() { + return super.getIdentityCredential(); + } } /** diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index 787dc6696cd3..0af18dfb0e3a 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.annotation.NonNull; +import android.security.identity.IdentityCredential; import android.security.keystore.AndroidKeyStoreProvider; import java.security.Signature; @@ -26,7 +27,8 @@ import javax.crypto.Mac; /** * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager. - * Currently the framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and + * {@link IdentityCredential} objects. * @hide */ public class CryptoObject { @@ -44,6 +46,10 @@ public class CryptoObject { mCrypto = mac; } + public CryptoObject(@NonNull IdentityCredential credential) { + mCrypto = credential; + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -69,11 +75,23 @@ public class CryptoObject { } /** + * Get {@link IdentityCredential} object. + * @return {@link IdentityCredential} object or null if this doesn't contain one. + */ + public IdentityCredential getIdentityCredential() { + return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null; + } + + /** * @hide * @return the opId associated with this object or 0 if none */ public final long getOpId() { - return mCrypto != null - ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0; + if (mCrypto == null) { + return 0; + } else if (mCrypto instanceof IdentityCredential) { + return ((IdentityCredential) mCrypto).getCredstoreOperationHandle(); + } + return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto); } }; diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 2377ccde5f89..89dac2aef68f 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -87,17 +87,31 @@ public abstract class CameraMetadata<TKey> { */ protected abstract <T> T getProtected(TKey key); - /** - * @hide - */ - protected void setNativeInstance(CameraMetadataNative nativeInstance) { + /** + * @hide + */ + protected void setNativeInstance(CameraMetadataNative nativeInstance) { mNativeInstance = nativeInstance; - } + } - /** - * @hide - */ - protected abstract Class<TKey> getKeyClass(); + /** + * Retrieves the native CameraMetadata* as a Java long. + * Returns 0 if mNativeInstance is null. + * + * @hide + */ + public long getNativeMetadataPtr() { + if (mNativeInstance == null) { + return 0; + } else { + return mNativeInstance.getMetadataPtr(); + } + } + + /** + * @hide + */ + protected abstract Class<TKey> getKeyClass(); /** * Returns a list of the keys contained in this map. diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 1fab6660cd73..3ae3d786af2a 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1703,6 +1703,15 @@ public class CameraMetadataNative implements Parcelable { /** + * Retrieves the pointer to the native CameraMetadata as a Java long. + * + * @hide + */ + public long getMetadataPtr() { + return mMetadataPtr; + } + + /** * Return a list containing keys of the given key class for all defined vendor tags. * * @hide diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 7bc45292c350..ff9d14510d4b 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -44,6 +44,7 @@ import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; +import android.security.identity.IdentityCredential; import android.util.Slog; import java.security.Signature; @@ -125,6 +126,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing super(mac); } + public CryptoObject(@NonNull IdentityCredential credential) { + super(credential); + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -148,6 +153,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public Mac getMac() { return super.getMac(); } + + /** + * Get {@link IdentityCredential} object. + * @return {@link IdentityCredential} object or null if this doesn't contain one. + */ + public @Nullable IdentityCredential getIdentityCredential() { + return super.getIdentityCredential(); + } } /** diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java index 2c5a113a93da..7e2db4a4fa95 100644 --- a/core/java/android/net/NetworkAgentConfig.java +++ b/core/java/android/net/NetworkAgentConfig.java @@ -22,6 +22,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Allows a network transport to provide the system with policy and configuration information about * a particular network when registering a {@link NetworkAgent}. This information cannot change once the agent is registered. @@ -52,23 +54,47 @@ public final class NetworkAgentConfig implements Parcelable { public boolean explicitlySelected; /** + * @return whether this network was explicitly selected by the user. + */ + public boolean isExplicitlySelected() { + return explicitlySelected; + } + + /** * Set if the user desires to use this network even if it is unvalidated. This field has meaning * only if {@link explicitlySelected} is true. If it is, this field must also be set to the * appropriate value based on previous user choice. * + * TODO : rename this field to match its accessor * @hide */ public boolean acceptUnvalidated; /** + * @return whether the system should accept this network even if it doesn't validate. + */ + public boolean isUnvalidatedConnectivityAcceptable() { + return acceptUnvalidated; + } + + /** * Whether the user explicitly set that this network should be validated even if presence of * only partial internet connectivity. * + * TODO : rename this field to match its accessor * @hide */ public boolean acceptPartialConnectivity; /** + * @return whether the system should validate this network even if it only offers partial + * Internet connectivity. + */ + public boolean isPartialConnectivityAcceptable() { + return acceptPartialConnectivity; + } + + /** * Set to avoid surfacing the "Sign in to network" notification. * if carrier receivers/apps are registered to handle the carrier-specific provisioning * procedure, a carrier specific provisioning notification will be placed. @@ -134,9 +160,11 @@ public final class NetworkAgentConfig implements Parcelable { * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. * + * This is not parceled, because it would not make sense. + * * @hide */ - public boolean hasShownBroken; + public transient boolean hasShownBroken; /** * The name of the legacy network type. It's a free-form string used in logging. @@ -163,6 +191,7 @@ public final class NetworkAgentConfig implements Parcelable { allowBypass = nac.allowBypass; explicitlySelected = nac.explicitlySelected; acceptUnvalidated = nac.acceptUnvalidated; + acceptPartialConnectivity = nac.acceptPartialConnectivity; subscriberId = nac.subscriberId; provisioningNotificationDisabled = nac.provisioningNotificationDisabled; skip464xlat = nac.skip464xlat; @@ -178,6 +207,43 @@ public final class NetworkAgentConfig implements Parcelable { private final NetworkAgentConfig mConfig = new NetworkAgentConfig(); /** + * Sets whether the network was explicitly selected by the user. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setExplicitlySelected(final boolean explicitlySelected) { + mConfig.explicitlySelected = explicitlySelected; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found not to offer + * Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setUnvalidatedConnectivityAcceptable( + final boolean unvalidatedConnectivityAcceptable) { + mConfig.acceptUnvalidated = unvalidatedConnectivityAcceptable; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found to only offer + * partial Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setPartialConnectivityAcceptable( + final boolean partialConnectivityAcceptable) { + mConfig.acceptPartialConnectivity = partialConnectivityAcceptable; + return this; + } + + /** * Sets the subscriber ID for this network. * * @return this builder, to facilitate chaining. @@ -245,6 +311,45 @@ public final class NetworkAgentConfig implements Parcelable { } @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final NetworkAgentConfig that = (NetworkAgentConfig) o; + return allowBypass == that.allowBypass + && explicitlySelected == that.explicitlySelected + && acceptUnvalidated == that.acceptUnvalidated + && acceptPartialConnectivity == that.acceptPartialConnectivity + && provisioningNotificationDisabled == that.provisioningNotificationDisabled + && skip464xlat == that.skip464xlat + && legacyType == that.legacyType + && Objects.equals(subscriberId, that.subscriberId) + && Objects.equals(legacyTypeName, that.legacyTypeName); + } + + @Override + public int hashCode() { + return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated, + acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId, + skip464xlat, legacyType, legacyTypeName); + } + + @Override + public String toString() { + return "NetworkAgentConfig {" + + " allowBypass = " + allowBypass + + ", explicitlySelected = " + explicitlySelected + + ", acceptUnvalidated = " + acceptUnvalidated + + ", acceptPartialConnectivity = " + acceptPartialConnectivity + + ", provisioningNotificationDisabled = " + provisioningNotificationDisabled + + ", subscriberId = '" + subscriberId + '\'' + + ", skip464xlat = " + skip464xlat + + ", legacyType = " + legacyType + + ", hasShownBroken = " + hasShownBroken + + ", legacyTypeName = '" + legacyTypeName + '\'' + + "}"; + } + + @Override public int describeContents() { return 0; } @@ -254,9 +359,12 @@ public final class NetworkAgentConfig implements Parcelable { out.writeInt(allowBypass ? 1 : 0); out.writeInt(explicitlySelected ? 1 : 0); out.writeInt(acceptUnvalidated ? 1 : 0); + out.writeInt(acceptPartialConnectivity ? 1 : 0); out.writeString(subscriberId); out.writeInt(provisioningNotificationDisabled ? 1 : 0); out.writeInt(skip464xlat ? 1 : 0); + out.writeInt(legacyType); + out.writeString(legacyTypeName); } public static final @NonNull Creator<NetworkAgentConfig> CREATOR = @@ -267,9 +375,12 @@ public final class NetworkAgentConfig implements Parcelable { networkAgentConfig.allowBypass = in.readInt() != 0; networkAgentConfig.explicitlySelected = in.readInt() != 0; networkAgentConfig.acceptUnvalidated = in.readInt() != 0; + networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0; networkAgentConfig.subscriberId = in.readString(); networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0; networkAgentConfig.skip464xlat = in.readInt() != 0; + networkAgentConfig.legacyType = in.readInt(); + networkAgentConfig.legacyTypeName = in.readString(); return networkAgentConfig; } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 33d613152bc1..b10abe7557d5 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -41,11 +41,11 @@ interface IUserManager { * END OF DO NOT MOVE */ - UserInfo createUser(in String name, in String userType, int flags); - UserInfo preCreateUser(in String userType); - UserInfo createProfileForUser(in String name, in String userType, int flags, int userId, + UserInfo createUserWithThrow(in String name, in String userType, int flags); + UserInfo preCreateUserWithThrow(in String userType); + UserInfo createProfileForUserWithThrow(in String name, in String userType, int flags, int userId, in String[] disallowedPackages); - UserInfo createRestrictedProfile(String name, int parentUserHandle); + UserInfo createRestrictedProfileWithThrow(String name, int parentUserHandle); void setUserEnabled(int userId); void setUserAdmin(int userId); void evictCredentialEncryptionKey(int userId); @@ -100,7 +100,7 @@ interface IUserManager { boolean isManagedProfile(int userId); boolean isDemoUser(int userId); boolean isPreCreated(int userId); - UserInfo createProfileForUserEvenWhenDisallowed(in String name, in String userType, int flags, + UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags, int userId, in String[] disallowedPackages); boolean isUserUnlockingOrUnlocked(int userId); int getUserIconBadgeResId(int userId); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 12e843c87481..08e4c3ae3683 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -28,6 +28,7 @@ import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.Activity; @@ -50,6 +51,7 @@ import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.provider.Settings; import android.telephony.TelephonyManager; +import android.util.AndroidException; import android.view.WindowManager.LayoutParams; import com.android.internal.R; @@ -77,8 +79,12 @@ public class UserManager { private static final String TAG = "UserManager"; @UnsupportedAppUsage private final IUserManager mService; + /** Holding the Application context (not constructor param context). */ private final Context mContext; + /** The userId of the constructor param context. To be used instead of mContext.getUserId(). */ + private final @UserIdInt int mUserId; + private Boolean mIsManagedProfileCached; private Boolean mIsProfileCached; @@ -87,6 +93,7 @@ public class UserManager { * This type of user cannot be created; it can only pre-exist on first boot. * @hide */ + @SystemApi public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; /** @@ -95,6 +102,7 @@ public class UserManager { * This is sometimes called an ordinary 'secondary user'. * @hide */ + @SystemApi public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; /** @@ -122,6 +130,7 @@ public class UserManager { * The intended purpose is for work profiles, which are managed by a corporate entity. * @hide */ + @SystemApi public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; /** @@ -130,6 +139,7 @@ public class UserManager { * This type of user cannot be created; it can only pre-exist on first boot. * @hide */ + @SystemApi public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS"; /** @@ -1435,6 +1445,62 @@ public class UserManager { public @UserOperationResult int getUserOperationResult() { return mUserOperationResult; } + + /** + * Returns a UserOperationException containing the same message and error code. + * @hide + */ + public static UserOperationException from(ServiceSpecificException exception) { + return new UserOperationException(exception.getMessage(), exception.errorCode); + } + } + + /** + * Converts the ServiceSpecificException into a UserOperationException or throws null; + * + * @param exception exception to convert. + * @param throwInsteadOfNull if an exception should be thrown or null returned. + * @return null if chosen not to throw exception. + * @throws UserOperationException + */ + private <T> T returnNullOrThrowUserOperationException(ServiceSpecificException exception, + boolean throwInsteadOfNull) throws UserOperationException { + if (throwInsteadOfNull) { + throw UserOperationException.from(exception); + } else { + return null; + } + } + + /** + * Thrown to indicate user operation failed. (Checked exception) + * @hide + */ + public static class CheckedUserOperationException extends AndroidException { + private final @UserOperationResult int mUserOperationResult; + + /** + * Constructs a CheckedUserOperationException with specific result code. + * + * @param message the detail message + * @param userOperationResult the result code + * @hide + */ + public CheckedUserOperationException(String message, + @UserOperationResult int userOperationResult) { + super(message); + mUserOperationResult = userOperationResult; + } + + /** Returns the operation result code. */ + public @UserOperationResult int getUserOperationResult() { + return mUserOperationResult; + } + + /** Return a ServiceSpecificException containing the same message and error code. */ + public ServiceSpecificException toServiceSpecificException() { + return new ServiceSpecificException(mUserOperationResult, getMessage()); + } } /** @hide */ @@ -1447,6 +1513,7 @@ public class UserManager { public UserManager(Context context, IUserManager service) { mService = service; mContext = context.getApplicationContext(); + mUserId = context.getUserId(); } /** @@ -1586,17 +1653,25 @@ public class UserManager { } /** - * Returns the user name of the user making this call. This call is only - * available to applications on the system image; it requires the - * {@code android.permission.MANAGE_USERS} or {@code android.permission.GET_ACCOUNTS_PRIVILEGED} - * permissions. + * Returns the user name of the context user. This call is only available to applications on + * the system image; it requires the {@code android.permission.MANAGE_USERS} or {@code + * android.permission.GET_ACCOUNTS_PRIVILEGED} permissions. + * * @return the user name */ - public String getUserName() { - try { - return mService.getUserName(); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional = true) + @UserHandleAware + public @NonNull String getUserName() { + if (UserHandle.myUserId() == mUserId) { + try { + return mService.getUserName(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } else { + UserInfo userInfo = getUserInfo(mUserId); + return userInfo == null ? "" : userInfo.name; } } @@ -1639,7 +1714,8 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser() { UserInfo user = getUserInfo(UserHandle.myUserId()); return user != null && user.isPrimary(); @@ -1676,22 +1752,26 @@ public class UserManager { * user. */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean isUserAdmin(@UserIdInt int userId) { UserInfo user = getUserInfo(userId); return user != null && user.isAdmin(); } /** - * Returns whether the current user is of the given user type, such as + * Returns whether the context user's user is of the given user type, such as * {@link UserManager#USER_TYPE_FULL_GUEST}. * * @return true if the user is of the given user type. * @hide */ + @SystemApi + @UserHandleAware @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull String userType) { try { - return mService.isUserOfType(UserHandle.myUserId(), userType); + return mService.isUserOfType(mUserId, userType); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -1707,6 +1787,7 @@ public class UserManager { * @return true if the userHandle user is of type userType * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull UserHandle userHandle, @NonNull String userType) { try { @@ -1786,7 +1867,8 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean isRestrictedProfile(@NonNull UserHandle user) { try { return mService.getUserInfo(user.getIdentifier()).isRestricted(); @@ -1799,6 +1881,7 @@ public class UserManager { * Checks if specified user can have restricted profile. * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean canHaveRestrictedProfile(@UserIdInt int userId) { try { return mService.canHaveRestrictedProfile(userId); @@ -1813,6 +1896,7 @@ public class UserManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles() { try { return mService.hasRestrictedProfiles(); @@ -1827,6 +1911,8 @@ public class UserManager { * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean isGuestUser(@UserIdInt int userId) { UserInfo user = getUserInfo(userId); return user != null && user.isGuest(); @@ -1839,7 +1925,8 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean isGuestUser() { UserInfo user = getUserInfo(UserHandle.myUserId()); return user != null && user.isGuest(); @@ -1861,47 +1948,45 @@ public class UserManager { } /** - * Checks if the calling app is running in a profile. - * - * @return whether the caller is in a profile. - * @hide - */ - public boolean isProfile() { - // No need for synchronization. Once it becomes non-null, it'll be non-null forever. - // Worst case we might end up calling the AIDL method multiple times but that's fine. - if (mIsProfileCached != null) { - return mIsProfileCached; - } - try { - mIsProfileCached = mService.isProfile(UserHandle.myUserId()); - return mIsProfileCached; - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** - * Checks if the specified user is a profile. + * Checks if the calling context user is running in a profile. * * Requires {@link android.Manifest.permission#MANAGE_USERS} or - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller - * must be in the same profile group of specified user. + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the + * caller must be in the same profile group of specified user. * - * @return whether the specified user is a profile. + * @return whether the caller is in a profile. * @hide */ + @SystemApi @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) - public boolean isProfile(@UserIdInt int userId) { + @UserHandleAware + public boolean isProfile() { + return isProfile(mUserId); + } + + private boolean isProfile(@UserIdInt int userId) { if (userId == UserHandle.myUserId()) { - return isProfile(); - } - try { - return mService.isProfile(userId); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + // No need for synchronization. Once it becomes non-null, it'll be non-null forever. + // Worst case we might end up calling the AIDL method multiple times but that's fine. + if (mIsProfileCached != null) { + return mIsProfileCached; + } + try { + mIsProfileCached = mService.isProfile(userId); + return mIsProfileCached; + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } else { + try { + return mService.isProfile(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } } } + /** * Checks if the calling app is running in a managed profile. * @@ -1931,7 +2016,8 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isManagedProfile(@UserIdInt int userId) { if (userId == UserHandle.myUserId()) { return isManagedProfile(); @@ -1949,6 +2035,8 @@ public class UserManager { * @return whether the caller is an ephemeral user. * @hide */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean isEphemeralUser() { return isUserEphemeral(UserHandle.myUserId()); } @@ -1957,6 +2045,8 @@ public class UserManager { * Returns whether the specified user is ephemeral. * @hide */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean isUserEphemeral(@UserIdInt int userId) { final UserInfo user = getUserInfo(userId); return user != null && user.isEphemeral(); @@ -1978,12 +2068,15 @@ public class UserManager { * * @param user The user to retrieve the running state for. */ - // Note this requires either INTERACT_ACROSS_USERS or MANAGE_USERS. + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isUserRunning(UserHandle user) { return isUserRunning(user.getIdentifier()); } /** {@hide} */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isUserRunning(@UserIdInt int userId) { try { return mService.isUserRunning(userId); @@ -2007,7 +2100,8 @@ public class UserManager { * * @param user The user to retrieve the running state for. */ - // Note this requires either INTERACT_ACROSS_USERS or MANAGE_USERS. + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isUserRunningOrStopping(UserHandle user) { try { // TODO: reconcile stopped vs stopping? @@ -2054,12 +2148,16 @@ public class UserManager { * @see Intent#ACTION_USER_UNLOCKED * @see Context#createDeviceProtectedStorageContext() */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isUserUnlocked(UserHandle user) { return isUserUnlocked(user.getIdentifier()); } /** {@hide} */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isUserUnlocked(@UserIdInt int userId) { try { return mService.isUserUnlocked(userId); @@ -2087,15 +2185,15 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(anyOf = { - Manifest.permission.MANAGE_USERS, - Manifest.permission.INTERACT_ACROSS_USERS - }) + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isUserUnlockingOrUnlocked(@NonNull UserHandle user) { return isUserUnlockingOrUnlocked(user.getIdentifier()); } /** {@hide} */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) { try { return mService.isUserUnlockingOrUnlocked(userId); @@ -2136,12 +2234,13 @@ public class UserManager { /** * Returns the UserInfo object describing a specific user. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @param userId the user handle of the user whose information is being requested. * @return the UserInfo object for a specific user. * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public UserInfo getUserInfo(@UserIdInt int userId) { try { return mService.getUserInfo(userId); @@ -2231,6 +2330,7 @@ public class UserManager { * @param userHandle the UserHandle of the user for whom to retrieve the restrictions. */ @UnsupportedAppUsage + @RequiresPermission(Manifest.permission.MANAGE_USERS) public boolean hasBaseUserRestriction(@UserRestrictionKey String restrictionKey, UserHandle userHandle) { try { @@ -2271,6 +2371,7 @@ public class UserManager { * android.content.ComponentName, String)} instead. */ @Deprecated + @RequiresPermission(Manifest.permission.MANAGE_USERS) public void setUserRestriction(String key, boolean value) { setUserRestriction(key, value, Process.myUserHandle()); } @@ -2278,7 +2379,6 @@ public class UserManager { /** * @hide * Sets the value of a specific restriction on a specific user. - * Requires the MANAGE_USERS permission. * @param key the key of the restriction * @param value the value for the restriction * @param userHandle the user whose restriction is to be changed. @@ -2288,6 +2388,7 @@ public class UserManager { * android.content.ComponentName, String)} instead. */ @Deprecated + @RequiresPermission(Manifest.permission.MANAGE_USERS) public void setUserRestriction(String key, boolean value, UserHandle userHandle) { try { mService.setUserRestriction(key, value, userHandle.getIdentifier()); @@ -2440,21 +2541,32 @@ public class UserManager { * Creates a user with the specified name and options. For non-admin users, default user * restrictions will be applied. * - * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}. + * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in + * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}. * - * @param name the user's name + * @param name the user's name * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. - * @param flags UserInfo flags that specify user properties. - * @see UserInfo + * @param flags UserInfo flags that specify user properties. + * @return the {@link UserInfo} object for the created user, + * or throws {@link UserOperationException} if the user could not be created + * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above + * (otherwise returns {@code null}). * - * @return the UserInfo object for the created user, or {@code null} if the user could not be - * created. + * @throws UserOperationException if the user could not be created and the calling app is + * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide + * @see UserInfo */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags) { try { - return mService.createUser(name, userType, flags); + return mService.createUserWithThrow(name, userType, flags); + } catch (ServiceSpecificException e) { + return returnNullOrThrowUserOperationException(e, + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2476,21 +2588,30 @@ public class UserManager { * * <p>All pre-created users are removed during system upgrade. * - * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}. + * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in + * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}. * * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. - * @return the UserInfo object for the created user, or {@code null} if the user could not be - * created. + * @return the {@link UserInfo} object for the created user, + * or throws {@link UserOperationException} if the user could not be created + * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above + * (otherwise returns {@code null}). * - * @throw {@link IllegalArgumentException} if {@code flags} contains - * {@link UserInfo#FLAG_MANAGED_PROFILE}. + * @throws UserOperationException if the user could not be created and the calling app is + * targeting {@link android.os.Build.VERSION_CODES#R} or above. * * @hide */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) - public @Nullable UserInfo preCreateUser(@NonNull String userType) { + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + public @Nullable UserInfo preCreateUser(@NonNull String userType) + throws UserOperationException { try { - return mService.preCreateUser(userType); + return mService.preCreateUserWithThrow(userType); + } catch (ServiceSpecificException e) { + return returnNullOrThrowUserOperationException(e, + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2500,16 +2621,28 @@ public class UserManager { * Creates a guest user and configures it. * @param context an application context * @param name the name to set for the user + * @return the {@link UserInfo} object for the created user, + * or throws {@link UserOperationException} if the user could not be created + * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above + * (otherwise returns {@code null}). + * + * @throws UserOperationException if the user could not be created and the calling app is + * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ - public UserInfo createGuest(Context context, String name) { + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + public UserInfo createGuest(Context context, String name) throws UserOperationException { UserInfo guest = null; try { - guest = mService.createUser(name, USER_TYPE_FULL_GUEST, 0); + guest = mService.createUserWithThrow(name, USER_TYPE_FULL_GUEST, 0); if (guest != null) { Settings.Secure.putStringForUser(context.getContentResolver(), Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id); } + } catch (ServiceSpecificException e) { + return returnNullOrThrowUserOperationException(e, + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2531,9 +2664,42 @@ public class UserManager { } /** + * Creates a user with the specified name and options as a profile of the context's user. + * + * @param name the user's name. + * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * @param disallowedPackages packages to not install for this profile. + * + * @return the {@link android.os.UserHandle} object for the created user, + * or throws {@link UserOperationException} if the user could not be created + * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above + * (otherwise returns {@code null}). + * + * @throws UserOperationException if the user could not be created and the calling app is + * targeting {@link android.os.Build.VERSION_CODES#R} or above. + * + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + @UserHandleAware + public @Nullable UserHandle createProfile(@NonNull String name, @NonNull String userType, + @Nullable String[] disallowedPackages) throws UserOperationException { + try { + return mService.createProfileForUserWithThrow(name, userType, 0, + mUserId, disallowedPackages).getUserHandle(); + } catch (ServiceSpecificException e) { + return returnNullOrThrowUserOperationException(e, + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Creates a user with the specified name and options as a profile of another user. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. - * The type of profile must be specified using the given flags. + * <p>Requires MANAGE_USERS. CREATE_USERS suffices for ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION * * @param name the user's name * @param flags flags that identify the type of user and other properties. @@ -2546,6 +2712,8 @@ public class UserManager { * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) @Deprecated public UserInfo createProfileForUser(String name, @UserInfoFlag int flags, @UserIdInt int userId) { @@ -2555,7 +2723,6 @@ public class UserManager { /** * Creates a user with the specified name and options as a profile of another user. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @param name the user's name * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. @@ -2566,6 +2733,8 @@ public class UserManager { * could not be created. * @hide */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public UserInfo createProfileForUser(String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId) { return createProfileForUser(name, userType, flags, userId, null); @@ -2582,14 +2751,26 @@ public class UserManager { * @param userId new user will be a profile of this user. * @param disallowedPackages packages that will not be installed in the profile being created. * - * @return the {@link UserInfo} object for the created user, or null if the user - * could not be created. + * @return the {@link UserInfo} object for the created user, + * or throws {@link UserOperationException} if the user could not be created + * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above + * (otherwise returns {@code null}). + * + * @throws UserOperationException if the user could not be created and the calling app is + * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public UserInfo createProfileForUser(String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) { + @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) + throws UserOperationException { try { - return mService.createProfileForUser(name, userType, flags, userId, disallowedPackages); + return mService.createProfileForUserWithThrow(name, userType, flags, userId, + disallowedPackages); + } catch (ServiceSpecificException e) { + return returnNullOrThrowUserOperationException(e, + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2598,17 +2779,21 @@ public class UserManager { /** * Similar to {@link #createProfileForUser(String, String, int, int, String[])} * except bypassing the checking of {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @see #createProfileForUser(String, String, int, int, String[]) * @hide */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public UserInfo createProfileForUserEvenWhenDisallowed(String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, - String[] disallowedPackages) { + String[] disallowedPackages) throws UserOperationException { try { - return mService.createProfileForUserEvenWhenDisallowed(name, userType, flags, + return mService.createProfileForUserEvenWhenDisallowedWithThrow(name, userType, flags, userId, disallowedPackages); + } catch (ServiceSpecificException e) { + return returnNullOrThrowUserOperationException(e, + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2619,19 +2804,30 @@ public class UserManager { * restrictions and adds shared accounts. * * @param name profile's name - * @return UserInfo object for the created user, or null if the user could not be created. + * @return the {@link UserInfo} object for the created user, + * or throws {@link UserOperationException} if the user could not be created + * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above + * (otherwise returns {@code null}). + * + * @throws UserOperationException if the user could not be created and the calling app is + * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ - public UserInfo createRestrictedProfile(String name) { + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + public UserInfo createRestrictedProfile(String name) throws UserOperationException { try { UserHandle parentUserHandle = Process.myUserHandle(); - UserInfo user = mService.createRestrictedProfile(name, + UserInfo user = mService.createRestrictedProfileWithThrow(name, parentUserHandle.getIdentifier()); if (user != null) { AccountManager.get(mContext).addSharedAccountsFromParentUser(parentUserHandle, UserHandle.of(user.id)); } return user; + } catch (ServiceSpecificException e) { + return returnNullOrThrowUserOperationException(e, + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2748,6 +2944,7 @@ public class UserManager { * @param accountOptions * @see #createUserCreationIntent(String, String, String, PersistableBundle) */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setSeedAccountData(int userId, String accountName, String accountType, PersistableBundle accountOptions) { try { @@ -2779,6 +2976,7 @@ public class UserManager { * @param userId * @return */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean markGuestForDeletion(@UserIdInt int userId) { try { return mService.markGuestForDeletion(userId); @@ -2790,8 +2988,6 @@ public class UserManager { /** * Sets the user as enabled, if such an user exists. * - * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. - * * <p>Note that the default is true, it's only that managed profiles might not be enabled. * Also ephemeral users can be disabled to indicate that their removal is in progress and they * shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled. @@ -2799,6 +2995,7 @@ public class UserManager { * @param userId the id of the profile to enable * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserEnabled(@UserIdInt int userId) { try { mService.setUserEnabled(userId); @@ -2832,6 +3029,7 @@ public class UserManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void evictCredentialEncryptionKey(@UserIdInt int userId) { try { mService.evictCredentialEncryptionKey(userId); @@ -2845,6 +3043,7 @@ public class UserManager { * <p>This API is not for use by third-party apps. It requires the {@code MANAGE_USERS} * permission.</p> */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserCount() { List<UserInfo> users = getUsers(); return users != null ? users.size() : 1; @@ -2963,11 +3162,11 @@ public class UserManager { /** * Returns information for Primary user. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @return the Primary user, null if not found. * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public @Nullable UserInfo getPrimaryUser() { try { return mService.getPrimaryUser(); @@ -2983,6 +3182,7 @@ public class UserManager { * @return true if more users can be added, false if limit has been reached. * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean canAddMoreUsers() { // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why // not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles @@ -3007,6 +3207,7 @@ public class UserManager { * @return true if more managed profiles can be added, false if limit has been reached. * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) { try { return mService.canAddMoreManagedProfiles(userId, allowedToRemoveOne); @@ -3036,12 +3237,15 @@ public class UserManager { * Note that this returns both enabled and not enabled profiles. See * {@link #getEnabledProfiles(int)} if you need only the enabled ones. * - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}. + * {@link android.Manifest.permission#CREATE_USERS} suffices if userId is the calling user. * @param userId profiles of this user will be returned. * @return the list of profiles. * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}, conditional = true) public List<UserInfo> getProfiles(@UserIdInt int userId) { try { return mService.getProfiles(userId, false /* enabledOnly */); @@ -3057,7 +3261,6 @@ public class UserManager { * @param otherUser one of the two user handles to check. * @return true if the two users are in the same profile group. * - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @hide */ @SystemApi @@ -3067,12 +3270,13 @@ public class UserManager { } /** - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * Checks if the 2 provided user ids belong to the same profile group. * @param userId one of the two user ids to check. * @param otherUserId one of the two user ids to check. * @return true if the two user ids are in the same profile group. * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) { try { return mService.isSameProfileGroup(userId, otherUserId); @@ -3085,12 +3289,15 @@ public class UserManager { * Returns list of the profiles of userId including userId itself. * Note that this returns only enabled. * - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}. + * {@link android.Manifest.permission#CREATE_USERS} suffices if userId is the calling user. * @param userId profiles of this user will be returned. * @return the list of profiles. * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}, conditional = true) public List<UserInfo> getEnabledProfiles(@UserIdInt int userId) { try { return mService.getProfiles(userId, true /* enabledOnly */); @@ -3115,6 +3322,28 @@ public class UserManager { } /** + * Returns a list of ids for profiles associated with the context user including the user + * itself. + * + * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles + * @return A non-empty list of UserHandles associated with the calling user. + * + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}, conditional = true) + @UserHandleAware + public @NonNull List<UserHandle> getUserProfiles(boolean enabledOnly) { + final int[] userIds = getProfileIds(mUserId, enabledOnly); + final List<UserHandle> result = new ArrayList<>(userIds.length); + for (int userId : userIds) { + result.add(UserHandle.of(userId)); + } + return result; + } + + /** * Returns a list of ids for profiles associated with the specified user including the user * itself. * @@ -3139,6 +3368,8 @@ public class UserManager { * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}, conditional = true) public int[] getProfileIdsWithDisabled(@UserIdInt int userId) { return getProfileIds(userId, false /* enabledOnly */); } @@ -3147,6 +3378,8 @@ public class UserManager { * @see #getProfileIds(int, boolean) * @hide */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}, conditional = true) public int[] getEnabledProfileIds(@UserIdInt int userId) { return getProfileIds(userId, true /* enabledOnly */); } @@ -3158,6 +3391,7 @@ public class UserManager { * * @hide */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) public int getCredentialOwnerProfile(@UserIdInt int userId) { try { return mService.getCredentialOwnerProfile(userId); @@ -3173,6 +3407,7 @@ public class UserManager { * @hide */ @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public UserInfo getProfileParent(@UserIdInt int userId) { try { return mService.getProfileParent(userId); @@ -3227,6 +3462,8 @@ public class UserManager { * * @see #isQuietModeEnabled(UserHandle) */ + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + Manifest.permission.MODIFY_QUIET_MODE}, conditional = true) public boolean requestQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) { return requestQuietModeEnabled(enableQuietMode, userHandle, null); } @@ -3258,6 +3495,7 @@ public class UserManager { * @see {@link #requestQuietModeEnabled(boolean, UserHandle)} * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean requestQuietModeEnabled( boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) { return requestQuietModeEnabled(enableQuietMode, userHandle, target, 0); @@ -3319,15 +3557,16 @@ public class UserManager { } /** - * Returns whether the calling app's user has a badge (generally to put on profiles' icons). + * Returns whether the user associated with the context has a badge (generally to put on + * profiles' icons). * * @return true if the user's icons should display a badge; false otherwise. - * * @see #getBadgedIconForUser more information about badging in general * @hide */ + @UserHandleAware public boolean hasBadge() { - return hasBadge(UserHandle.myUserId()); + return hasBadge(mUserId); } /** @@ -3485,11 +3724,12 @@ public class UserManager { /** * Removes a user and all associated data. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @param userId the integer handle of the user. * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean removeUser(@UserIdInt int userId) { try { return mService.removeUser(userId); @@ -3507,7 +3747,8 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull UserHandle user) { if (user == null) { throw new IllegalArgumentException("user cannot be null"); @@ -3524,6 +3765,8 @@ public class UserManager { * @see {@link #removeUser(int)} * @hide */ + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { try { return mService.removeUserEvenWhenDisallowed(userId); @@ -3534,12 +3777,12 @@ public class UserManager { /** * Updates the user's name. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @param userId the user's integer id * @param name the new name for the user * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@UserIdInt int userId, String name) { try { mService.setUserName(userId, name); @@ -3549,16 +3792,16 @@ public class UserManager { } /** - * Updates the calling user's name. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * Updates the context user's name. * * @param name the new name for the user * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @UserHandleAware public void setUserName(@Nullable String name) { - setUserName(getUserHandle(), name); + setUserName(mUserId, name); } /** @@ -3567,35 +3810,41 @@ public class UserManager { * @param icon the bitmap to set as the photo. * @hide */ - public void setUserIcon(@UserIdInt int userId, Bitmap icon) { + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public void setUserIcon(@UserIdInt int userId, @NonNull Bitmap icon) + throws UserOperationException { try { mService.setUserIcon(userId, icon); + } catch (ServiceSpecificException e) { + throw UserOperationException.from(e); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Sets the calling user's photo. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * Sets the context user's photo. * * @param icon the bitmap to set as the photo. * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_USERS) - public void setUserIcon(@NonNull Bitmap icon) { - setUserIcon(getUserHandle(), icon); + @UserHandleAware + public void setUserIcon(@NonNull Bitmap icon) throws UserOperationException { + setUserIcon(mUserId, icon); } /** - * Returns a file descriptor for the user's photo. PNG data can be read from this file. + * Returns a bitmap of the user's photo * @param userId the user whose photo we want to read. * @return a {@link Bitmap} of the user's photo, or null if there's no photo. * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default. * @hide */ @UnsupportedAppUsage + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public Bitmap getUserIcon(@UserIdInt int userId) { try { ParcelFileDescriptor fd = mService.getUserIcon(userId); @@ -3616,9 +3865,7 @@ public class UserManager { } /** - * Returns a Bitmap for the calling user's photo. - * Requires {@link android.Manifest.permission#MANAGE_USERS} - * or {@link android.Manifest.permission#GET_ACCOUNTS_PRIVILEGED} permissions. + * Returns a Bitmap for the context user's photo. * * @return a {@link Bitmap} of the user's photo, or null if there's no photo. * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default. @@ -3627,8 +3874,9 @@ public class UserManager { @SystemApi @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) + @UserHandleAware public @Nullable Bitmap getUserIcon() { - return getUserIcon(getUserHandle()); + return getUserIcon(mUserId); } /** @@ -3805,6 +4053,7 @@ public class UserManager { * @hide * Set restrictions that should apply to any future guest user that's created. */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDefaultGuestRestrictions(Bundle restrictions) { try { mService.setDefaultGuestRestrictions(restrictions); @@ -3817,6 +4066,7 @@ public class UserManager { * @hide * Gets the default guest restrictions. */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public Bundle getDefaultGuestRestrictions() { try { return mService.getDefaultGuestRestrictions(); @@ -3847,6 +4097,7 @@ public class UserManager { * @param accountType The account type of the account to check for * @return whether the seed account was found */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean someUserHasSeedAccount(String accountName, String accountType) { try { return mService.someUserHasSeedAccount(accountName, accountType); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index eee8fb13d3a6..0742a20a9fd6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5883,6 +5883,22 @@ public final class Settings { "dark_mode_dialog_seen"; /** + * Custom time when Dark theme is scheduled to activate. + * Represented as milliseconds from midnight (e.g. 79200000 == 10pm). + * @hide + */ + public static final String DARK_THEME_CUSTOM_START_TIME = + "dark_theme_custom_start_time"; + + /** + * Custom time when Dark theme is scheduled to deactivate. + * Represented as milliseconds from midnight (e.g. 79200000 == 10pm). + * @hide + */ + public static final String DARK_THEME_CUSTOM_END_TIME = + "dark_theme_custom_end_time"; + + /** * Defines value returned by {@link android.service.autofill.UserData#getMaxUserDataSize()}. * * @hide @@ -7706,6 +7722,14 @@ public final class Settings { public static final String UI_NIGHT_MODE = "ui_night_mode"; /** + * The current night mode that has been overrided by the system. Owned + * and controlled by UiModeManagerService. Constants are as per + * UiModeManager. + * @hide + */ + public static final String UI_NIGHT_MODE_OVERRIDE = "ui_night_mode_override"; + + /** * Whether screensavers are enabled. * @hide */ diff --git a/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl b/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl new file mode 100644 index 000000000000..ea55ebbadd88 --- /dev/null +++ b/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.euicc; + +/** @hide */ +oneway interface IEuiccServiceDumpResultCallback { + void onComplete(in String logs); +}
\ No newline at end of file diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 9a76a1b4eb79..e3fd8d297316 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -31,6 +31,7 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; @@ -188,6 +189,7 @@ public abstract class WallpaperService extends Service { DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT; final InsetsState mInsetsState = new InsetsState(); final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); + private final Point mSurfaceSize = new Point(); final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); @@ -838,12 +840,13 @@ public abstract class WallpaperService extends Service { } else { mLayout.surfaceInsets.set(0, 0, 0, 0); } + final int relayoutResult = mSession.relayout( mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, View.VISIBLE, 0, -1, mWinFrame, mContentInsets, mVisibleInsets, mStableInsets, mBackdropFrame, mDisplayCutout, mMergedConfiguration, mSurfaceControl, - mInsetsState); + mInsetsState, mSurfaceSize); if (mSurfaceControl.isValid()) { mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); mSurfaceControl.release(); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 8439f5aad0b1..0558204af4c9 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -30,6 +30,7 @@ import libcore.timezone.ZoneInfoDB; import java.io.PrintWriter; import java.text.SimpleDateFormat; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -382,6 +383,28 @@ public class TimeUtils { } /** + * This method is used to find if a clock time is inclusively between two other clock times + * @param reference The time of the day we want check if it is between start and end + * @param start The start time reference + * @param end The end time + * @return true if the reference time is between the two clock times, and false otherwise. + */ + public static boolean isTimeBetween(@NonNull LocalTime reference, + @NonNull LocalTime start, + @NonNull LocalTime end) { + // ////////E----+-----S//////// + if ((reference.isBefore(start) && reference.isAfter(end) + // -----+----S//////////E------ + || (reference.isBefore(end) && reference.isBefore(start) && start.isBefore(end)) + // ---------S//////////E---+--- + || (reference.isAfter(end) && reference.isAfter(start)) && start.isBefore(end))) { + return false; + } else { + return true; + } + } + + /** * Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now. * * @hide diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 8f25d89fe08c..e3446e1f7b57 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -18,6 +18,7 @@ package android.view; import android.content.ClipData; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; @@ -90,6 +91,7 @@ interface IWindowSession { * since it was last displayed. * @param outSurface Object in which is placed the new display surface. * @param insetsState The current insets state in the system. + * @param outSurfaceSize The width and height of the surface control * * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS}, * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}. @@ -101,7 +103,7 @@ interface IWindowSession { out Rect outBackdropFrame, out DisplayCutout.ParcelableWrapper displayCutout, out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, - out InsetsState insetsState); + out InsetsState insetsState, out Point outSurfaceSize); /* * Notify the window manager that an application is relaunching and diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index db89d9588507..411e910e1af1 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -19,6 +19,8 @@ package android.view; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -807,20 +809,41 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @Override - public void setSystemBarsAppearance(@Appearance int appearance) { - if (mViewRoot.mWindowAttributes.insetsFlags.appearance != appearance) { - mViewRoot.mWindowAttributes.insetsFlags.appearance = appearance; + public void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask) { + mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED; + final InsetsFlags insetsFlags = mViewRoot.mWindowAttributes.insetsFlags; + if (insetsFlags.appearance != appearance) { + insetsFlags.appearance = (insetsFlags.appearance & ~mask) | (appearance & mask); mViewRoot.mWindowAttributesChanged = true; mViewRoot.scheduleTraversals(); } } @Override + public @Appearance int getSystemBarsAppearance() { + if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) { + // We only return the requested appearance, not the implied one. + return 0; + } + return mViewRoot.mWindowAttributes.insetsFlags.appearance; + } + + @Override public void setSystemBarsBehavior(@Behavior int behavior) { + mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) { mViewRoot.mWindowAttributes.insetsFlags.behavior = behavior; mViewRoot.mWindowAttributesChanged = true; mViewRoot.scheduleTraversals(); } } + + @Override + public @Appearance int getSystemBarsBehavior() { + if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) { + // We only return the requested behavior, not the implied one. + return 0; + } + return mViewRoot.mWindowAttributes.insetsFlags.behavior; + } } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 38416eeede32..bcc9e41b8ab0 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -128,6 +128,8 @@ public final class SurfaceControl implements Parcelable { int l, int t, int r, int b); private static native void nativeSetCornerRadius(long transactionObj, long nativeObject, float cornerRadius); + private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject, + int blurRadius); private static native void nativeSetLayerStack(long transactionObj, long nativeObject, int layerStack); @@ -2505,6 +2507,20 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets the background blur radius of the {@link SurfaceControl}. + * + * @param sc SurfaceControl. + * @param radius Blur radius in pixels. + * @return itself. + * @hide + */ + public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) { + checkPreconditions(sc); + nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius); + return this; + } + + /** * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6724e9d3289d..377a7644f21b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22,6 +22,7 @@ import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LO import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_CONTINUE_ON_SUBTREE; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static java.lang.Math.max; @@ -111,6 +112,7 @@ import android.view.AccessibilityIterators.WordTextSegmentIterator; import android.view.ContextMenu.ContextMenuInfo; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; +import android.view.WindowInsetsAnimationCallback.DispatchMode; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; @@ -949,22 +951,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static boolean sAcceptZeroSizeDragShadow; /** - * Prior to Q, {@link #dispatchApplyWindowInsets} had some issues: - * <ul> - * <li>The modified insets changed by {@link #onApplyWindowInsets} were passed to the - * entire view hierarchy in prefix order, including siblings as well as siblings of parents - * further down the hierarchy. This violates the basic concepts of the view hierarchy, and - * thus, the hierarchical dispatching mechanism was hard to use for apps.</li> - * - * <li>Dispatch was stopped after the insets were fully consumed. This is somewhat confusing - * for developers, but more importantly, by adding more granular information to - * {@link WindowInsets} it becomes really cumbersome to define what consumed actually means - * </li> - * </ul> - * + * Prior to R, {@link #dispatchApplyWindowInsets} had an issue: + * <p>The modified insets changed by {@link #onApplyWindowInsets} were passed to the + * entire view hierarchy in prefix order, including siblings as well as siblings of parents + * further down the hierarchy. This violates the basic concepts of the view hierarchy, and + * thus, the hierarchical dispatching mechanism was hard to use for apps. + * <p> * In order to make window inset dispatching work properly, we dispatch window insets - * in the view hierarchy in a proper hierarchical manner and don't stop dispatching if the - * insets are consumed if this flag is set to {@code false}. + * in the view hierarchy in a proper hierarchical manner if this flag is set to {@code false}. */ static boolean sBrokenInsetsDispatch; @@ -4784,7 +4778,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; - + private SendAccessibilityEventThrottle mSendStateChangedAccessibilityEvent; private UnsetPressedState mUnsetPressedState; /** @@ -5231,7 +5225,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P; sBrokenInsetsDispatch = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL - || targetSdkVersion < Build.VERSION_CODES.Q; + || targetSdkVersion < Build.VERSION_CODES.R; sBrokenWindowBackground = targetSdkVersion < Build.VERSION_CODES.Q; @@ -8154,13 +8148,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) { dispatchPopulateAccessibilityEvent(event); } - // In the beginning we called #isShown(), so we know that getParent() is not null. + SendAccessibilityEventThrottle throttle = getThrottleForAccessibilityEvent(event); + if (throttle != null) { + throttle.post(event); + } else { + requestParentSendAccessibilityEvent(event); + } + } + + private void requestParentSendAccessibilityEvent(AccessibilityEvent event) { ViewParent parent = getParent(); if (parent != null) { getParent().requestSendAccessibilityEvent(this, event); } } + private SendAccessibilityEventThrottle getThrottleForAccessibilityEvent( + AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + if (mSendViewScrolledAccessibilityEvent == null) { + mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + } + return mSendViewScrolledAccessibilityEvent; + } + boolean isStateContentChanged = (event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION) != 0; + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + && isStateContentChanged) { + if (mSendStateChangedAccessibilityEvent == null) { + mSendStateChangedAccessibilityEvent = new SendAccessibilityEventThrottle(); + } + return mSendStateChangedAccessibilityEvent; + } + return null; + } + + private void clearAccessibilityThrottles() { + cancel(mSendViewScrolledAccessibilityEvent); + cancel(mSendStateChangedAccessibilityEvent); + } + /** * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then * to its children for adding their text content to the event. Note that the @@ -10381,8 +10408,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION); + sendAccessibilityEventUnchecked(event); + } } /** @@ -11100,7 +11131,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Sets a {@link WindowInsetsAnimationCallback} to be notified about animations of windows that * cause insets. - * + * <p> + * When setting a listener, it's {@link WindowInsetsAnimationCallback#getDispatchMode() dispatch + * mode} will be retrieved and recorded until another listener will be set. + * </p> * @param listener The listener to set. */ public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) { @@ -15986,10 +16020,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { notifySubtreeAccessibilityStateChangedIfNeeded(); - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); - } + postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; @@ -18712,10 +18743,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) { - if (mSendViewScrolledAccessibilityEvent == null) { - mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + event.setScrollDeltaX(dx); + event.setScrollDeltaY(dy); + sendAccessibilityEventUnchecked(event); } - mSendViewScrolledAccessibilityEvent.post(dx, dy); } /** @@ -20013,7 +20047,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); - cancel(mSendViewScrolledAccessibilityEvent); + clearAccessibilityThrottles(); stopNestedScroll(); // Anything that started animating right before detach should already @@ -28997,49 +29031,67 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** - * Resuable callback for sending - * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. - */ - private class SendViewScrolledAccessibilityEvent implements Runnable { + private class SendAccessibilityEventThrottle implements Runnable { public volatile boolean mIsPending; - public int mDeltaX; - public int mDeltaY; + private AccessibilityEvent mAccessibilityEvent; - public void post(int dx, int dy) { - mDeltaX += dx; - mDeltaY += dy; + public void post(AccessibilityEvent accessibilityEvent) { + updateWithAccessibilityEvent(accessibilityEvent); if (!mIsPending) { mIsPending = true; - postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); + postDelayed(this, + ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); } } @Override public void run() { if (AccessibilityManager.getInstance(mContext).isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_VIEW_SCROLLED); - event.setScrollDeltaX(mDeltaX); - event.setScrollDeltaY(mDeltaY); - sendAccessibilityEventUnchecked(event); + requestParentSendAccessibilityEvent(mAccessibilityEvent); } reset(); } - private void reset() { + public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) { + mAccessibilityEvent = accessibilityEvent; + } + + public void reset() { mIsPending = false; - mDeltaX = 0; - mDeltaY = 0; + mAccessibilityEvent = null; } + } /** - * Remove the pending callback for sending a + * Resuable callback for sending * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. */ + private class SendViewScrolledAccessibilityEvent extends SendAccessibilityEventThrottle { + public int mDeltaX; + public int mDeltaY; + + @Override + public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) { + super.updateWithAccessibilityEvent(accessibilityEvent); + mDeltaX += accessibilityEvent.getScrollDeltaX(); + mDeltaY += accessibilityEvent.getScrollDeltaY(); + accessibilityEvent.setScrollDeltaX(mDeltaX); + accessibilityEvent.setScrollDeltaY(mDeltaY); + } + + @Override + public void reset() { + super.reset(); + mDeltaX = 0; + mDeltaY = 0; + } + } + /** + * Remove the pending callback for sending a throttled accessibility event. + */ @UnsupportedAppUsage - private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) { + private void cancel(@Nullable SendAccessibilityEventThrottle callback) { if (callback == null || !callback.mIsPending) return; removeCallbacks(callback); callback.reset(); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 047d7da7536f..e6470a7d1e27 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -17,11 +17,14 @@ package android.view; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; +import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_CONTINUE_ON_SUBTREE; +import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_STOP; import android.animation.LayoutTransition; import android.annotation.CallSuper; import android.annotation.IdRes; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UiThread; import android.compat.annotation.UnsupportedAppUsage; @@ -45,6 +48,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Pools; @@ -52,6 +56,7 @@ import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.WindowInsetsAnimationCallback.AnimationBounds; +import android.view.WindowInsetsAnimationCallback.DispatchMode; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -607,6 +612,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager int mChildUnhandledKeyListeners = 0; /** + * Current dispatch mode of animation events + * + * @see WindowInsetsAnimationCallback#getDispatchMode() + */ + private @DispatchMode int mInsetsAnimationDispatchMode = DISPATCH_MODE_CONTINUE_ON_SUBTREE; + + /** * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild. * * @see #startActionModeForChild(View, android.view.ActionMode.Callback) @@ -7170,6 +7182,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { insets = super.dispatchApplyWindowInsets(insets); + if (insets.isConsumed()) { + return insets; + } if (View.sBrokenInsetsDispatch) { return brokenDispatchApplyWindowInsets(insets); } else { @@ -7178,13 +7193,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) { - if (!insets.isConsumed()) { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - insets = getChildAt(i).dispatchApplyWindowInsets(insets); - if (insets.isConsumed()) { - break; - } + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + insets = getChildAt(i).dispatchApplyWindowInsets(insets); + if (insets.isConsumed()) { + break; } } return insets; @@ -7199,9 +7212,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) { + super.setWindowInsetsAnimationCallback(listener); + mInsetsAnimationDispatchMode = listener != null + ? listener.getDispatchMode() + : DISPATCH_MODE_CONTINUE_ON_SUBTREE; + } + + @Override public void dispatchWindowInsetsAnimationPrepare( @NonNull InsetsAnimation animation) { super.dispatchWindowInsetsAnimationPrepare(animation); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation); @@ -7212,7 +7236,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @NonNull public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { - super.dispatchWindowInsetsAnimationStart(animation, bounds); + bounds = super.dispatchWindowInsetsAnimationStart(animation, bounds); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return bounds; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds); @@ -7224,6 +7251,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @NonNull public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) { insets = super.dispatchWindowInsetsAnimationProgress(insets); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return insets; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationProgress(insets); @@ -7234,6 +7264,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { super.dispatchWindowInsetsAnimationFinish(animation); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationFinish(animation); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 32622233d81e..09ebd005e7ed 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -27,15 +27,24 @@ import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE; import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; +import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; +import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; +import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE; +import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -427,6 +436,10 @@ public final class ViewRootImpl implements ViewParent, FallbackEventHandler mFallbackEventHandler; Choreographer mChoreographer; + // used in relayout to get SurfaceControl size + // for BLAST adapter surface setup + private final Point mSurfaceSize = new Point(); + final Rect mTempRect; // used in the transaction to not thrash the heap. final Rect mVisRect; // used to retrieve visible rect of focused view. private final Rect mTempBoundsRect = new Rect(); // used to set the size of the bounds surface. @@ -1937,10 +1950,28 @@ public final class ViewRootImpl implements ViewParent, final int type = inOutParams.type; final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST; - if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0) { - inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; - } else if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE) != 0) { - inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_SWIPE; + if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) { + inOutParams.insetsFlags.appearance = 0; + if ((sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { + inOutParams.insetsFlags.appearance |= APPEARANCE_LOW_PROFILE_BARS; + } + if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) { + inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_STATUS_BARS; + } + if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0) { + inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS; + } + } + + if ((inOutParams.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) { + if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0 + || (flags & FLAG_FULLSCREEN) != 0) { + inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + } else if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE) != 0) { + inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_SWIPE; + } else { + inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_TOUCH; + } } if ((inOutParams.privateFlags & PRIVATE_FLAG_FIT_INSETS_CONTROLLED) != 0) { @@ -7306,14 +7337,13 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout, - mPendingMergedConfiguration, mSurfaceControl, mTempInsets); + mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize); if (mSurfaceControl.isValid()) { if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { mSurface.copyFrom(mSurfaceControl); } else { - mSurface.transferFrom(getOrCreateBLASTSurface( - (int) (mView.getMeasuredWidth() * appScale + 0.5f), - (int) (mView.getMeasuredHeight() * appScale + 0.5f))); + mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x, + mSurfaceSize.y)); } } else { destroySurface(); diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 06c24b0fa3f3..0a2a45b44523 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -42,6 +42,7 @@ import android.content.Intent; import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; +import android.view.View.OnApplyWindowInsetsListener; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.inputmethod.EditorInfo; @@ -96,13 +97,20 @@ public final class WindowInsets { private final boolean mCompatIgnoreVisibility; /** - * Since new insets may be added in the future that existing apps couldn't - * know about, this fully empty constant shouldn't be made available to apps - * since it would allow them to inadvertently consume unknown insets by returning it. - * @hide + * A {@link WindowInsets} instance for which {@link #isConsumed()} returns {@code true}. + * <p> + * This can be used during insets dispatch in the view hierarchy by returning this value from + * {@link View#onApplyWindowInsets(WindowInsets)} or + * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets)} to stop dispatch + * the insets to its children to avoid traversing the entire view hierarchy. + * <p> + * The application should return this instance once it has taken care of all insets on a certain + * level in the view hierarchy, and doesn't need to dispatch to its children anymore for better + * performance. + * + * @see #isConsumed() */ - @UnsupportedAppUsage - public static final WindowInsets CONSUMED; + public static final @NonNull WindowInsets CONSUMED; static { CONSUMED = new WindowInsets((Rect) null, null, false, false, null); @@ -456,7 +464,11 @@ public final class WindowInsets { * Returns a copy of this WindowInsets with the cutout fully consumed. * * @return A modified copy of this WindowInsets + * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is + * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED} + * instead to stop dispatching insets. */ + @Deprecated @NonNull public WindowInsets consumeDisplayCutout() { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, @@ -504,7 +516,11 @@ public final class WindowInsets { * Returns a copy of this WindowInsets with the system window insets fully consumed. * * @return A modified copy of this WindowInsets + * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is + * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED} + * instead to stop dispatching insets. */ + @Deprecated @NonNull public WindowInsets consumeSystemWindowInsets() { return new WindowInsets(null, null, @@ -754,7 +770,11 @@ public final class WindowInsets { * Returns a copy of this WindowInsets with the stable insets fully consumed. * * @return A modified copy of this WindowInsets + * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is + * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED} + * instead to stop dispatching insets. */ + @Deprecated @NonNull public WindowInsets consumeStableInsets() { return consumeSystemWindowInsets(); diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index e84c3e33c000..53d493985b32 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Insets; @@ -24,6 +25,9 @@ import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.animation.Interpolator; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Interface that allows the application to listen to animation events for windows that cause * insets. @@ -31,6 +35,52 @@ import android.view.animation.Interpolator; public interface WindowInsetsAnimationCallback { /** + * Return value for {@link #getDispatchMode()}: Dispatching of animation events should + * stop at this level in the view hierarchy, and no animation events should be dispatch to the + * subtree of the view hierarchy. + */ + int DISPATCH_MODE_STOP = 0; + + /** + * Return value for {@link #getDispatchMode()}: Dispatching of animation events should + * continue in the view hierarchy. + */ + int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; + + /** @hide */ + @IntDef(prefix = { "DISPATCH_MODE_" }, value = { + DISPATCH_MODE_STOP, + DISPATCH_MODE_CONTINUE_ON_SUBTREE + }) + @Retention(RetentionPolicy.SOURCE) + @interface DispatchMode {} + + /** + * Retrieves the dispatch mode of this listener. Dispatch of the all animation events is + * hierarchical: It will starts at the root of the view hierarchy and then traverse it and + * invoke the callback of the specific {@link View} that is being traversed. + * The method may return either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that + * animation events should be propagated to the subtree of the view hierarchy, or + * {@link #DISPATCH_MODE_STOP} to stop dispatching. In that case, all animation callbacks + * related to the animation passed in will be stopped from propagating to the subtree of the + * hierarchy. + * <p> + * Note that this method will only be invoked once when + * {@link View#setWindowInsetsAnimationCallback setting the listener} and then the framework + * will use the recorded result. + * <p> + * Also note that returning {@link #DISPATCH_MODE_STOP} here behaves the same way as returning + * {@link WindowInsets#CONSUMED} during the regular insets dispatch in + * {@link View#onApplyWindowInsets}. + * + * @return Either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that dispatching of + * animation events will continue to the subtree of the view hierarchy, or + * {@link #DISPATCH_MODE_STOP} to indicate that animation events will stop dispatching. + */ + @DispatchMode + int getDispatchMode(); + + /** * Called when an insets animation is about to start and before the views have been laid out in * the end state of the animation. The ordering of events during an insets animation is the * following: @@ -75,10 +125,11 @@ public interface WindowInsetsAnimationCallback { * <p> * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical: * It will starts at the root of the view hierarchy and then traverse it and invoke the callback - * of the specific {@link View} that is being traversed. The method my return a modified + * of the specific {@link View} that is being traversed. The method may return a modified * instance of the bounds by calling {@link AnimationBounds#inset} to indicate that a part of * the insets have been used to offset or clip its children, and the children shouldn't worry - * about that part anymore. + * about that part anymore. Furthermore, if {@link #getDispatchMode()} returns + * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore. * * @param animation The animation that is about to start. * @param bounds The bounds in which animation happens. @@ -102,7 +153,9 @@ public interface WindowInsetsAnimationCallback { * The method may return a modified instance by calling * {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have * been used to offset or clip its children, and the children shouldn't worry about that part - * anymore. + * anymore. Furthermore, if {@link #getDispatchMode()} returns + * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore. + * * TODO: Introduce a way to map (type -> InsetAnimation) so app developer can query animation * for a given type e.g. callback.getAnimation(type) OR controller.getAnimation(type). * Or on the controller directly? @@ -237,6 +290,7 @@ public interface WindowInsetsAnimationCallback { * Class representing the range of an {@link InsetsAnimation} */ final class AnimationBounds { + private final Insets mLowerBound; private final Insets mUpperBound; diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 9d7f292dbdf5..f292ca4facbf 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -29,7 +29,7 @@ import java.lang.annotation.RetentionPolicy; /** * Interface to control windows that generate insets. * - * TODO Needs more information and examples once the API is more baked. + * TODO(118118435): Needs more information and examples once the API is more baked. */ public interface WindowInsetsController { @@ -205,21 +205,47 @@ public interface WindowInsetsController { /** * Controls the appearance of system bars. + * <p> + * For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}: + * <pre> + * setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS) + * </pre> + * And the following statement clears it: + * <pre> + * setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS) + * </pre> * * @param appearance Bitmask of {@link Appearance} flags. - * @see Appearance + * @param mask Specifies which flags of appearance should be changed. + * @see #getSystemBarsAppearance + */ + void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask); + + /** + * Retrieves the requested appearance of system bars. + * + * @return The requested bitmask of system bar appearance controlled by this window. + * @see #setSystemBarsAppearance(int, int) */ - void setSystemBarsAppearance(@Appearance int appearance); + @Appearance int getSystemBarsAppearance(); /** * Controls the behavior of system bars. * * @param behavior Determines how the bars behave when being hidden by the application. - * @see Behavior + * @see #getSystemBarsBehavior */ void setSystemBarsBehavior(@Behavior int behavior); /** + * Retrieves the requested behavior of system bars. + * + * @return the system bar behavior controlled by this window. + * @see #setSystemBarsBehavior(int) + */ + @Behavior int getSystemBarsBehavior(); + + /** * @hide */ InsetsState getState(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 290a13d3feb1..cd9dee4f7329 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1857,18 +1857,32 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000; /** + * Flag to indicate that the window is controlling the appearance of system bars. So we + * don't need to adjust it by reading its system UI flags for compatibility. + * @hide + */ + public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000; + + /** + * Flag to indicate that the window is controlling the behavior of system bars. So we don't + * need to adjust it by reading its window flags or system UI flags for compatibility. + * @hide + */ + public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000; + + /** * Flag to indicate that the window is controlling how it fits window insets on its own. * So we don't need to adjust its attributes for fitting window insets. * @hide */ - public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x04000000; + public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000; /** * Flag to indicate that the window only draws the bottom bar background so that we don't * extend it to system bar areas at other sides. * @hide */ - public static final int PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND = 0x08000000; + public static final int PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND = 0x20000000; /** * An internal annotation for flags that can be specified to {@link #softInputMode}. @@ -1970,6 +1984,14 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, name = "COLOR_SPACE_AGNOSTIC"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED, + equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED, + name = "APPEARANCE_CONTROLLED"), + @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_BEHAVIOR_CONTROLLED, + equals = PRIVATE_FLAG_BEHAVIOR_CONTROLLED, + name = "BEHAVIOR_CONTROLLED"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED, equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED, name = "FIT_INSETS_CONTROLLED"), diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 1312a9b1c158..9f2784889cab 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -18,6 +18,7 @@ package android.view; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; @@ -159,7 +160,8 @@ public class WindowlessWindowManager implements IWindowSession { Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { State state = null; synchronized (this) { state = mStateForWindow.get(window.asBinder()); diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index d119b2e1992b..969bda9da5a0 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; +import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -66,6 +67,10 @@ import java.util.List; * <p> * The easiest way to use this class is to call one of the static methods that constructs * everything you need and returns a new Toast object. + * <p> + * Note that + * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbars</a> are + * preferred for brief messages while the app is in the foreground. * * <div class="special reference"> * <h3>Developer Guides</h3> @@ -100,7 +105,8 @@ public class Toast { */ public static final int LENGTH_LONG = 1; - final Context mContext; + private final Binder mToken; + private final Context mContext; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) final TN mTN; @UnsupportedAppUsage @@ -126,7 +132,8 @@ public class Toast { */ public Toast(@NonNull Context context, @Nullable Looper looper) { mContext = context; - mTN = new TN(context.getPackageName(), looper); + mToken = new Binder(); + mTN = new TN(context.getPackageName(), mToken, looper); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( @@ -149,9 +156,9 @@ public class Toast { try { if (mIsCustomToast) { - service.enqueueToast(pkg, tn, mDuration, displayId); + service.enqueueToast(pkg, mToken, tn, mDuration, displayId); } else { - service.enqueueTextToast(pkg, tn, mDuration, displayId); + service.enqueueTextToast(pkg, mToken, tn, mDuration, displayId); } } catch (RemoteException e) { // Empty @@ -169,8 +176,16 @@ public class Toast { /** * Set the view to show. + * * @see #getView + * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the + * {@link #makeText(Context, CharSequence, int)} method, or use a + * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a> + * when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps + * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background + * will not have custom toast views displayed. */ + @Deprecated public void setView(View view) { mIsCustomToast = true; mNextView = view; @@ -178,7 +193,14 @@ public class Toast { /** * Return the view. + * * @see #setView + * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the + * {@link #makeText(Context, CharSequence, int)} method, or use a + * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a> + * when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps + * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background + * will not have custom toast views displayed. */ public View getView() { mIsCustomToast = true; @@ -416,7 +438,8 @@ public class Toast { WindowManager mWM; - String mPackageName; + final String mPackageName; + final Binder mToken; @GuardedBy("mCallbacks") private final List<Callback> mCallbacks = new ArrayList<>(); @@ -424,7 +447,7 @@ public class Toast { static final long SHORT_DURATION_TIMEOUT = 4000; static final long LONG_DURATION_TIMEOUT = 7000; - TN(String packageName, @Nullable Looper looper) { + TN(String packageName, Binder token, @Nullable Looper looper) { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. final WindowManager.LayoutParams params = mParams; @@ -440,6 +463,7 @@ public class Toast { | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mPackageName = packageName; + mToken = token; if (looper == null) { // Use Looper.myLooper() if looper is not specified. @@ -471,7 +495,7 @@ public class Toast { // handleShow() mNextView = null; try { - getService().cancelToast(mPackageName, TN.this); + getService().cancelToast(mPackageName, mToken); } catch (RemoteException e) { } break; @@ -601,7 +625,7 @@ public class Toast { // Now that we've removed the view it's safe for the server to release // the resources. try { - getService().finishToken(mPackageName, this); + getService().finishToken(mPackageName, mToken); } catch (RemoteException e) { } diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp index 1a9e8d077e21..2aca31733599 100644 --- a/core/jni/android/graphics/SurfaceTexture.cpp +++ b/core/jni/android/graphics/SurfaceTexture.cpp @@ -23,10 +23,10 @@ #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> -#include <gui/Surface.h> -#include <gui/surfacetexture/SurfaceTexture.h> #include <gui/BufferQueue.h> -#include <gui/surfacetexture/surface_texture_platform.h> +#include <gui/Surface.h> +#include <surfacetexture/SurfaceTexture.h> +#include <surfacetexture/surface_texture_platform.h> #include "core_jni_helpers.h" diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp index f2a51ad04885..8cf1d2cbfaae 100644 --- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp +++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp @@ -27,14 +27,14 @@ #include "android_runtime/android_view_Surface.h" #include "android_runtime/android_graphics_SurfaceTexture.h" -#include <gui/Surface.h> -#include <gui/surfacetexture/SurfaceTexture.h> #include <gui/IGraphicBufferProducer.h> #include <gui/IProducerListener.h> -#include <ui/GraphicBuffer.h> -#include <system/window.h> +#include <gui/Surface.h> #include <hardware/camera3.h> +#include <surfacetexture/SurfaceTexture.h> #include <system/camera_metadata.h> +#include <system/window.h> +#include <ui/GraphicBuffer.h> #include <stdint.h> #include <inttypes.h> diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 50a60a917185..e0f9571472da 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -560,6 +560,14 @@ static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionOb transaction->setCornerRadius(ctrl, cornerRadius); } +static void nativeSetBackgroundBlurRadius(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint blurRadius) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); + transaction->setBackgroundBlurRadius(ctrl, blurRadius); +} + static void nativeSetLayerStack(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint layerStack) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -1369,6 +1377,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetWindowCrop }, {"nativeSetCornerRadius", "(JJF)V", (void*)nativeSetCornerRadius }, + {"nativeSetBackgroundBlurRadius", "(JJI)V", + (void*)nativeSetBackgroundBlurRadius }, {"nativeSetLayerStack", "(JJI)V", (void*)nativeSetLayerStack }, {"nativeSetShadowRadius", "(JJF)V", diff --git a/core/jni/android_view_TextureLayer.cpp b/core/jni/android_view_TextureLayer.cpp index 5491a338023c..40f618025f99 100644 --- a/core/jni/android_view_TextureLayer.cpp +++ b/core/jni/android_view_TextureLayer.cpp @@ -19,12 +19,9 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <android/surface_texture_jni.h> #include "core_jni_helpers.h" -#include <android_runtime/android_graphics_SurfaceTexture.h> -#include <gui/IGraphicBufferProducer.h> -#include <gui/surfacetexture/surface_texture_platform.h> -#include <gui/surfacetexture/SurfaceTexture.h> #include <hwui/Paint.h> #include <SkMatrix.h> #include <DeferredLayerUpdater.h> @@ -61,10 +58,8 @@ static void TextureLayer_setTransform(JNIEnv* env, jobject clazz, static void TextureLayer_setSurfaceTexture(JNIEnv* env, jobject clazz, jlong layerUpdaterPtr, jobject surface) { DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); - auto consumer = SurfaceTexture_getSurfaceTexture(env, surface); - auto producer = SurfaceTexture_getProducer(env, surface); - layer->setSurfaceTexture(AutoTextureRelease( - ASurfaceTexture_create(consumer, producer))); + ASurfaceTexture* surfaceTexture = ASurfaceTexture_fromSurfaceTexture(env, surface); + layer->setSurfaceTexture(AutoTextureRelease(surfaceTexture, &ASurfaceTexture_release)); } static void TextureLayer_updateSurfaceTexture(JNIEnv* env, jobject clazz, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 9d02d105e11e..2535fcfd509e 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1471,8 +1471,7 @@ static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list, // Create profile directory for this user. std::string actualCurUserProfile = StringPrintf("%s/%d", kCurProfileDirPath, user_id); - PrepareDir(actualCurUserProfile.c_str(), DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, - fail_fn); + PrepareDir(actualCurUserProfile, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); for (int i = 0; i < size; i += 3) { jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i)); diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index a85c8f4e798b..ce03727314d1 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2562,4 +2562,10 @@ enum PageId { // CATEGORY: SETTINGS // OS: R OPEN_SUPPORTED_LINKS = 1824; + + // OPEN: Settings > Display > Dark theme > Set start time dialog + DIALOG_DARK_THEME_SET_START_TIME = 1825; + + // OPEN: Settings > Display > Dark theme > Set end time dialog + DIALOG_DARK_THEME_SET_END_TIME = 1826; } diff --git a/core/proto/android/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto index 70627edf2cb3..c6925f448a58 100644 --- a/core/proto/android/server/animationadapter.proto +++ b/core/proto/android/server/animationadapter.proto @@ -50,6 +50,7 @@ message AnimationSpecProto { optional WindowAnimationSpecProto window = 1; optional MoveAnimationSpecProto move = 2; optional AlphaAnimationSpecProto alpha = 3; + optional RotationAnimationSpecProto rotate = 4; } /* represents WindowAnimationSpec */ @@ -76,3 +77,12 @@ message AlphaAnimationSpecProto { optional float to = 2; optional int64 duration_ms = 3; } + +/* represents RotationAnimationSpec */ +message RotationAnimationSpecProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional float start_luma = 1; + optional float end_luma = 2; + optional int64 duration_ms = 3; +} diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 0ae11a106a54..0f03e69e6c93 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -156,4 +156,5 @@ enum EventId { SET_PACKAGES_PROTECTED = 129; SET_FACTORY_RESET_PROTECTION = 130; SET_COMMON_CRITERIA_MODE = 131; + ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 490c47796472..a8da2d922d56 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2211,12 +2211,12 @@ <!-- Must be required by a NetworkService to ensure that only the system can bind to it. - <p>Protection level: signature + <p>Protection level: signature|telephony @SystemApi @hide --> <permission android:name="android.permission.BIND_TELEPHONY_NETWORK_SERVICE" - android:protectionLevel="signature" /> + android:protectionLevel="signature|telephony" /> <!-- @SystemApi Allows an application to manage embedded subscriptions (those on a eUICC) through EuiccManager APIs. @@ -2369,7 +2369,7 @@ <permission android:name="android.permission.MANAGE_USERS" android:protectionLevel="signature|privileged" /> - <!-- @hide Allows an application to create, remove users and get the list of + <!-- @SystemApi @hide Allows an application to create, remove users and get the list of users on the device. Applications holding this permission can only create restricted, guest, managed, demo, and ephemeral users. For creating other kind of users, {@link android.Manifest.permission#MANAGE_USERS} is needed. diff --git a/core/res/res/anim/screen_rotate_0_enter.xml b/core/res/res/anim/screen_rotate_0_enter.xml index 93cf3652d185..629be7ea2d30 100644 --- a/core/res/res/anim/screen_rotate_0_enter.xml +++ b/core/res/res/anim/screen_rotate_0_enter.xml @@ -1,25 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* -** Copyright 2010, 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. -*/ ---> + ~ 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. + --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_shortAnimTime" /> + android:shareInterpolator="false"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@interpolator/screen_rotation_alpha_in" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_screen_rotation_fade_in" /> </set> diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml index 37d5a4115621..fa046a036855 100644 --- a/core/res/res/anim/screen_rotate_0_exit.xml +++ b/core/res/res/anim/screen_rotate_0_exit.xml @@ -1,22 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* -** Copyright 2010, 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. -*/ ---> + ~ 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. + --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/screen_rotation_alpha_out" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/anim/screen_rotate_0_frame.xml b/core/res/res/anim/screen_rotate_0_frame.xml deleted file mode 100644 index 5ea9bf8205e3..000000000000 --- a/core/res/res/anim/screen_rotate_0_frame.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2012, 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. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_shortAnimTime" /> -</set> diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml index 688a8d5bb2aa..889a615e07f4 100644 --- a/core/res/res/anim/screen_rotate_180_enter.xml +++ b/core/res/res/anim/screen_rotate_180_enter.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="180" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_180" /> </set> diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml index 58a1868bd398..766fcfae1f91 100644 --- a/core/res/res/anim/screen_rotate_180_exit.xml +++ b/core/res/res/anim/screen_rotate_180_exit.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="-180" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_180" /> </set>
\ No newline at end of file diff --git a/core/res/res/anim/screen_rotate_alpha.xml b/core/res/res/anim/screen_rotate_alpha.xml index c49ef9cafd39..2cac982e24b4 100644 --- a/core/res/res/anim/screen_rotate_alpha.xml +++ b/core/res/res/anim/screen_rotate_alpha.xml @@ -20,8 +20,8 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/decelerate_quint" + android:interpolator="@interpolator/screen_rotation_alpha_out" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml index b16d5fc761ee..87fd25ea4603 100644 --- a/core/res/res/anim/screen_rotate_minus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml @@ -18,19 +18,17 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase anim + android:shareInterpolator="false"> <rotate android:fromDegrees="-90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <rotate android:fromDegrees="-90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/screen_rotation_alpha_in" + android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" + android:duration="@android:integer/config_screen_rotation_fade_in" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml index 0927dd30ceb3..c3aee14dc235 100644 --- a/core/res/res/anim/screen_rotate_minus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml @@ -18,26 +18,16 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase animation + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <scale android:fromXScale="100%" android:toXScale="100%p" - android:fromYScale="100%" android:toYScale="100%p" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> - <rotate android:fromDegrees="0" android:toDegrees="90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/screen_rotation_alpha_out" + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml index 86a8d24cbbcc..8849db421e75 100644 --- a/core/res/res/anim/screen_rotate_plus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml @@ -18,19 +18,16 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase animation + android:shareInterpolator="false"> <rotate android:fromDegrees="90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <rotate android:fromDegrees="90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:fillEnabled="true" + android:interpolator="@interpolator/screen_rotation_alpha_in" + android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" + android:duration="@android:integer/config_screen_rotation_fade_in" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml index fd786f9afce0..de84c3bd08fc 100644 --- a/core/res/res/anim/screen_rotate_plus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml @@ -18,26 +18,16 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase animation + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="-90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <scale android:fromXScale="100%" android:toXScale="100%p" - android:fromYScale="100%" android:toYScale="100%p" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> - <rotate android:fromDegrees="0" android:toDegrees="-90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/screen_rotation_alpha_out" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/res/res/interpolator/screen_rotation_alpha_in.xml new file mode 100644 index 000000000000..9c566a7c8f23 --- /dev/null +++ b/core/res/res/interpolator/screen_rotation_alpha_in.xml @@ -0,0 +1,22 @@ +<?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. + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.15" + android:controlY1="0.45" + android:controlX2="0.33" + android:controlY2="1"/> diff --git a/core/res/res/interpolator/screen_rotation_alpha_out.xml b/core/res/res/interpolator/screen_rotation_alpha_out.xml new file mode 100644 index 000000000000..73a37d4f1aa5 --- /dev/null +++ b/core/res/res/interpolator/screen_rotation_alpha_out.xml @@ -0,0 +1,22 @@ +<?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. + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.57" + android:controlY1="0" + android:controlX2="0.71" + android:controlY2=".43"/> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 94f3b8ae6714..0895edc1da70 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1520,6 +1520,22 @@ <!-- Managing a media projection session, e.g, for screen recording or taking screenshots.--> <flag name="mediaProjection" value="0x20" /> + <!-- Use the camera device or record video. + + <p>For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} + and above, a foreground service will not be able to access the camera if this type is + not specified in the manifest and in + {@link android.app.Service#startForeground(int, android.app.Notification, int)}. + --> + <flag name="camera" value="0x40" /> + <!--Use the microphone device or record audio. + + <p>For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} + and above, a foreground service will not be able to access the microphone if this type + is not specified in the manifest and in + {@link android.app.Service#startForeground(int, android.app.Notification, int)}. + --> + <flag name="microphone" value="0x80" /> </attr> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 85ebbd5fe7a9..7fd444a1a416 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -147,6 +147,24 @@ <integer name="config_activityShortDur">150</integer> <integer name="config_activityDefaultDur">220</integer> + <!-- Fade out time for screen rotation --> + <integer name="config_screen_rotation_fade_out">116</integer> + + <!-- Fade in time for screen rotation --> + <integer name="config_screen_rotation_fade_in">233</integer> + + <!-- Fade in delay time for screen rotation --> + <integer name="config_screen_rotation_fade_in_delay">100</integer> + + <!-- Total time for 90 degree screen rotation animations --> + <integer name="config_screen_rotation_total_90">333</integer> + + <!-- Total time for 180 degree screen rotation animation --> + <integer name="config_screen_rotation_total_180">433</integer> + + <!-- Total time for the rotation background color transition --> + <integer name="config_screen_rotation_color_transition">200</integer> + <!-- The duration (in milliseconds) of the tooltip show/hide animations. --> <integer name="config_tooltipAnimTime">150</integer> @@ -263,6 +281,10 @@ before automatically restore the default connection. Set -1 if the connection does not require auto-restore. --> <!-- the 6th element indicates boot-time dependency-met value. --> + <!-- NOTE: The telephony module is no longer reading the configuration below for available + APN types. The set of APN types and relevant settings are specified within the telephony + module and are non-configurable. Whether or not data connectivity over a cellular network + is available at all is controlled by the flag: config_moble_data_capable. --> <string-array translatable="false" name="networkAttributes"> <item>"wifi,1,1,1,-1,true"</item> <item>"mobile,0,0,0,-1,true"</item> @@ -1849,6 +1871,12 @@ Note: This config is deprecated, please use config_defaultSms instead. --> <string name="default_sms_application" translatable="false">com.android.messaging</string> + <!-- Flag indicating whether the current device allows data. + If true, this means that the device supports data connectivity through + the telephony network. + This can be overridden to false for devices that support voice and/or sms . --> + <bool name="config_mobile_data_capable">true</bool> + <!-- Default web browser. This is the package name of the application that will be the default browser when the device first boots. Afterwards the user can select whatever browser app they wish to use as the default. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 69e8c78d92f0..527798d45b96 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -310,6 +310,7 @@ <java-symbol type="bool" name="config_sip_wifi_only" /> <java-symbol type="bool" name="config_sms_capable" /> <java-symbol type="bool" name="config_sms_utf8_support" /> + <java-symbol type="bool" name="config_mobile_data_capable" /> <java-symbol type="bool" name="config_suspendWhenScreenOffDueToProximity" /> <java-symbol type="bool" name="config_swipeDisambiguation" /> <java-symbol type="bool" name="config_syncstorageengine_masterSyncAutomatically" /> @@ -1805,7 +1806,6 @@ <!-- From services --> <java-symbol type="anim" name="screen_rotate_0_enter" /> <java-symbol type="anim" name="screen_rotate_0_exit" /> - <java-symbol type="anim" name="screen_rotate_0_frame" /> <java-symbol type="anim" name="screen_rotate_180_enter" /> <java-symbol type="anim" name="screen_rotate_180_exit" /> <java-symbol type="anim" name="screen_rotate_180_frame" /> @@ -1981,6 +1981,7 @@ <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" /> <java-symbol type="integer" name="config_brightness_ramp_rate_fast" /> <java-symbol type="integer" name="config_brightness_ramp_rate_slow" /> + <java-symbol type="integer" name="config_screen_rotation_color_transition" /> <java-symbol type="layout" name="am_compat_mode_dialog" /> <java-symbol type="layout" name="launch_warning" /> <java-symbol type="layout" name="safe_mode" /> diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java index f9a6a5c2af4d..0a21875fd77d 100644 --- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java +++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java @@ -116,25 +116,28 @@ public class NotificationHistoryTest { @Test public void testAddNotificationsToWrite() { NotificationHistory history = new NotificationHistory(); - HistoricalNotification n = getHistoricalNotification(0); + HistoricalNotification n = getHistoricalNotification(3); HistoricalNotification n2 = getHistoricalNotification(1); + HistoricalNotification n5 = getHistoricalNotification(0); history.addNotificationToWrite(n2); history.addNotificationToWrite(n); + history.addNotificationToWrite(n5); NotificationHistory secondHistory = new NotificationHistory(); - HistoricalNotification n3 = getHistoricalNotification(2); - HistoricalNotification n4 = getHistoricalNotification(3); + HistoricalNotification n3 = getHistoricalNotification(4); + HistoricalNotification n4 = getHistoricalNotification(2); secondHistory.addNotificationToWrite(n4); secondHistory.addNotificationToWrite(n3); history.addNotificationsToWrite(secondHistory); - assertThat(history.getNotificationsToWrite().size()).isEqualTo(4); - assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n2); + assertThat(history.getNotificationsToWrite().size()).isEqualTo(5); + assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n3); assertThat(history.getNotificationsToWrite().get(1)).isSameAs(n); assertThat(history.getNotificationsToWrite().get(2)).isSameAs(n4); - assertThat(history.getNotificationsToWrite().get(3)).isSameAs(n3); - assertThat(history.getHistoryCount()).isEqualTo(4); + assertThat(history.getNotificationsToWrite().get(3)).isSameAs(n2); + assertThat(history.getNotificationsToWrite().get(4)).isSameAs(n5); + assertThat(history.getHistoryCount()).isEqualTo(5); assertThat(history.getPooledStringsToWrite()).asList().contains(n2.getChannelName()); assertThat(history.getPooledStringsToWrite()).asList().contains(n4.getPackage()); diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java index dfd762b17307..47c4286b2afb 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -470,7 +470,7 @@ public class PackageParserTest { PackageParser.collectCertificates(p, false); PackageInfo pi = PackageParser.generatePackageInfo(p, apexInfo, flags); - assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); + assertEquals("com.google.android.tzdata2", pi.applicationInfo.packageName); assertTrue(pi.applicationInfo.enabled); assertEquals(28, pi.applicationInfo.targetSdkVersion); assertEquals(191000070, pi.applicationInfo.longVersionCode); @@ -479,7 +479,7 @@ public class PackageParserTest { assertEquals("Bundle[{com.android.vending.derived.apk.id=1}]", pi.applicationInfo.metaData.toString()); - assertEquals("com.google.android.tzdata", pi.packageName); + assertEquals("com.google.android.tzdata2", pi.packageName); assertEquals(191000070, pi.getLongVersionCode()); assertNotNull(pi.signingInfo); assertTrue(pi.signingInfo.getApkContentsSigners().length > 0); diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp new file mode 100644 index 000000000000..a2fcef56b780 --- /dev/null +++ b/core/tests/overlaytests/host/Android.bp @@ -0,0 +1,30 @@ +// 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. + +java_test_host { + name: "OverlayHostTests", + srcs: ["src/**/*.java"], + libs: ["tradefed"], + test_suites: ["general-tests"], + target_required: [ + "OverlayHostTests_NonPlatformSignatureOverlay", + "OverlayHostTests_PlatformSignatureStaticOverlay", + "OverlayHostTests_PlatformSignatureOverlay", + "OverlayHostTests_UpdateOverlay", + "OverlayHostTests_FrameworkOverlayV1", + "OverlayHostTests_FrameworkOverlayV2", + "OverlayHostTests_AppOverlayV1", + "OverlayHostTests_AppOverlayV2", + ], +} diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk index e7348d52adfe..d58d9393c0b8 100644 --- a/core/tests/overlaytests/host/Android.mk +++ b/core/tests/overlaytests/host/Android.mk @@ -14,23 +14,6 @@ LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-java-files-under,src) -LOCAL_MODULE_TAGS := tests -LOCAL_MODULE := OverlayHostTests -LOCAL_JAVA_LIBRARIES := tradefed -LOCAL_COMPATIBILITY_SUITE := general-tests -LOCAL_TARGET_REQUIRED_MODULES := \ - OverlayHostTests_NonPlatformSignatureOverlay \ - OverlayHostTests_PlatformSignatureStaticOverlay \ - OverlayHostTests_PlatformSignatureOverlay \ - OverlayHostTests_UpdateOverlay \ - OverlayHostTests_FrameworkOverlayV1 \ - OverlayHostTests_FrameworkOverlayV2 \ - OverlayHostTests_AppOverlayV1 \ - OverlayHostTests_AppOverlayV2 -include $(BUILD_HOST_JAVA_LIBRARY) - # Include to build test-apps. include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/identity/MODULE_LICENSE_APACHE2 b/identity/MODULE_LICENSE_APACHE2 new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/identity/MODULE_LICENSE_APACHE2 diff --git a/identity/NOTICE b/identity/NOTICE new file mode 100644 index 000000000000..64aaa8dbd68e --- /dev/null +++ b/identity/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2009, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/identity/OWNERS b/identity/OWNERS new file mode 100644 index 000000000000..533d90b2cd13 --- /dev/null +++ b/identity/OWNERS @@ -0,0 +1,3 @@ +swillden@google.com +zeuthen@google.com + diff --git a/identity/java/android/security/identity/AccessControlProfile.java b/identity/java/android/security/identity/AccessControlProfile.java new file mode 100644 index 000000000000..10e451c97707 --- /dev/null +++ b/identity/java/android/security/identity/AccessControlProfile.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +import java.security.cert.X509Certificate; + +/** + * A class used to specify access controls. + */ +public class AccessControlProfile { + private AccessControlProfileId mAccessControlProfileId = new AccessControlProfileId(0); + private X509Certificate mReaderCertificate = null; + private boolean mUserAuthenticationRequired = true; + private long mUserAuthenticationTimeout = 0; + + private AccessControlProfile() { + } + + AccessControlProfileId getAccessControlProfileId() { + return mAccessControlProfileId; + } + + long getUserAuthenticationTimeout() { + return mUserAuthenticationTimeout; + } + + boolean isUserAuthenticationRequired() { + return mUserAuthenticationRequired; + } + + X509Certificate getReaderCertificate() { + return mReaderCertificate; + } + + /** + * A builder for {@link AccessControlProfile}. + */ + public static final class Builder { + private AccessControlProfile mProfile; + + /** + * Each access control profile has numeric identifier that must be unique within the + * context of a Credential and may be used to reference the profile. + * + * <p>By default, the resulting {@link AccessControlProfile} will require user + * authentication with a timeout of zero, thus requiring the holder to authenticate for + * every presentation where data elements using this access control profile is used.</p> + * + * @param accessControlProfileId the access control profile identifier. + */ + public Builder(@NonNull AccessControlProfileId accessControlProfileId) { + mProfile = new AccessControlProfile(); + mProfile.mAccessControlProfileId = accessControlProfileId; + } + + /** + * Set whether user authentication is required. + * + * <p>This should be used sparingly since disabling user authentication on just a single + * data element can easily create a + * <a href="https://en.wikipedia.org/wiki/Relay_attack">Relay Attack</a> if the device + * on which the credential is stored is compromised.</p> + * + * @param userAuthenticationRequired Set to true if user authentication is required, + * false otherwise. + * @return The builder. + */ + public @NonNull Builder setUserAuthenticationRequired(boolean userAuthenticationRequired) { + mProfile.mUserAuthenticationRequired = userAuthenticationRequired; + return this; + } + + /** + * Sets the authentication timeout to use. + * + * <p>The authentication timeout specifies the amount of time, in milliseconds, for which a + * user authentication is valid, if user authentication is required (see + * {@link #setUserAuthenticationRequired(boolean)}).</p> + * + * <p>If the timeout is zero, then authentication is always required for each reader + * session.</p> + * + * @param userAuthenticationTimeoutMillis the authentication timeout, in milliseconds. + * @return The builder. + */ + public @NonNull Builder setUserAuthenticationTimeout(long userAuthenticationTimeoutMillis) { + mProfile.mUserAuthenticationTimeout = userAuthenticationTimeoutMillis; + return this; + } + + /** + * Sets the reader certificate to use when checking access control. + * + * <p>If set, this is checked against the certificate chain presented by + * reader. The access check is fulfilled only if one of the certificates + * in the chain, matches the certificate set by this method.</p> + * + * @param readerCertificate the certificate to use for the access control check. + * @return The builder. + */ + public @NonNull Builder setReaderCertificate(@NonNull X509Certificate readerCertificate) { + mProfile.mReaderCertificate = readerCertificate; + return this; + } + + /** + * Creates a new {@link AccessControlProfile} from the data supplied to the builder. + * + * @return The created {@link AccessControlProfile} object. + */ + public @NonNull AccessControlProfile build() { + return mProfile; + } + } +} diff --git a/identity/java/android/security/identity/AccessControlProfileId.java b/identity/java/android/security/identity/AccessControlProfileId.java new file mode 100644 index 000000000000..3d5945065ad7 --- /dev/null +++ b/identity/java/android/security/identity/AccessControlProfileId.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +/** + * A class used to wrap an access control profile identifiers. + */ +public class AccessControlProfileId { + private int mId = 0; + + /** + * Constructs a new object holding a numerical identifier. + * + * @param id the identifier. + */ + public AccessControlProfileId(int id) { + this.mId = id; + } + + /** + * Gets the numerical identifier wrapped by this object. + * + * @return the identifier. + */ + public int getId() { + return this.mId; + } +} diff --git a/identity/java/android/security/identity/AlreadyPersonalizedException.java b/identity/java/android/security/identity/AlreadyPersonalizedException.java new file mode 100644 index 000000000000..1933882aa4bf --- /dev/null +++ b/identity/java/android/security/identity/AlreadyPersonalizedException.java @@ -0,0 +1,43 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to create a credential which already exists. + */ +public class AlreadyPersonalizedException extends IdentityCredentialException { + /** + * Constructs a new {@link AlreadyPersonalizedException} exception. + * + * @param message the detail message. + */ + public AlreadyPersonalizedException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link AlreadyPersonalizedException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public AlreadyPersonalizedException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/CipherSuiteNotSupportedException.java b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java new file mode 100644 index 000000000000..e7a6c8969482 --- /dev/null +++ b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java @@ -0,0 +1,43 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to use a cipher suite which isn't supported. + */ +public class CipherSuiteNotSupportedException extends IdentityCredentialException { + /** + * Constructs a new {@link CipherSuiteNotSupportedException} exception. + * + * @param message the detail message. + */ + public CipherSuiteNotSupportedException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link CipherSuiteNotSupportedException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public CipherSuiteNotSupportedException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java new file mode 100644 index 000000000000..c520331ab72d --- /dev/null +++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java @@ -0,0 +1,424 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyAgreement; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +class CredstoreIdentityCredential extends IdentityCredential { + + private static final String TAG = "CredstoreIdentityCredential"; + private String mCredentialName; + private @IdentityCredentialStore.Ciphersuite int mCipherSuite; + private Context mContext; + private ICredential mBinder; + + CredstoreIdentityCredential(Context context, String credentialName, + @IdentityCredentialStore.Ciphersuite int cipherSuite, + ICredential binder) { + mContext = context; + mCredentialName = credentialName; + mCipherSuite = cipherSuite; + mBinder = binder; + } + + private KeyPair mEphemeralKeyPair = null; + private SecretKey mSecretKey = null; + private SecretKey mReaderSecretKey = null; + private int mEphemeralCounter; + private int mReadersExpectedEphemeralCounter; + + private void ensureEphemeralKeyPair() { + if (mEphemeralKeyPair != null) { + return; + } + try { + // This PKCS#12 blob is generated in credstore, using BoringSSL. + // + // The main reason for this convoluted approach and not just sending the decomposed + // key-pair is that this would require directly using (device-side) BouncyCastle which + // is tricky due to various API hiding efforts. So instead we have credstore generate + // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL + // doesn't support not using encryption when building a PKCS#12 blob). + // + byte[] pkcs12 = mBinder.createEphemeralKeyPair(); + String alias = "ephemeralKey"; + char[] password = {}; + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12); + ks.load(bais, password); + PrivateKey privKey = (PrivateKey) ks.getKey(alias, password); + + Certificate cert = ks.getCertificate(alias); + PublicKey pubKey = cert.getPublicKey(); + + mEphemeralKeyPair = new KeyPair(pubKey, privKey); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } catch (KeyStoreException + | CertificateException + | UnrecoverableKeyException + | NoSuchAlgorithmException + | IOException e) { + throw new RuntimeException("Unexpected exception ", e); + } + } + + @Override + public @NonNull KeyPair createEphemeralKeyPair() { + ensureEphemeralKeyPair(); + return mEphemeralKeyPair; + } + + @Override + public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) + throws InvalidKeyException { + try { + byte[] uncompressedForm = + Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey); + mBinder.setReaderEphemeralPublicKey(uncompressedForm); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + + ensureEphemeralKeyPair(); + + try { + KeyAgreement ka = KeyAgreement.getInstance("ECDH"); + ka.init(mEphemeralKeyPair.getPrivate()); + ka.doPhase(readerEphemeralPublicKey, true); + byte[] sharedSecret = ka.generateSecret(); + + byte[] salt = new byte[1]; + byte[] info = new byte[0]; + + salt[0] = 0x01; + byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); + mSecretKey = new SecretKeySpec(derivedKey, "AES"); + + salt[0] = 0x00; + derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); + mReaderSecretKey = new SecretKeySpec(derivedKey, "AES"); + + mEphemeralCounter = 0; + mReadersExpectedEphemeralCounter = 0; + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Error performing key agreement", e); + } + } + + @Override + public @NonNull byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext) { + byte[] messageCiphertextAndAuthTag = null; + try { + ByteBuffer iv = ByteBuffer.allocate(12); + iv.putInt(0, 0x00000000); + iv.putInt(4, 0x00000001); + iv.putInt(8, mEphemeralCounter); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array()); + cipher.init(Cipher.ENCRYPT_MODE, mSecretKey, encryptionParameterSpec); + messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext); + } catch (BadPaddingException + | IllegalBlockSizeException + | NoSuchPaddingException + | InvalidKeyException + | NoSuchAlgorithmException + | InvalidAlgorithmParameterException e) { + throw new RuntimeException("Error encrypting message", e); + } + mEphemeralCounter += 1; + return messageCiphertextAndAuthTag; + } + + @Override + public @NonNull byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext) + throws MessageDecryptionException { + ByteBuffer iv = ByteBuffer.allocate(12); + iv.putInt(0, 0x00000000); + iv.putInt(4, 0x00000000); + iv.putInt(8, mReadersExpectedEphemeralCounter); + byte[] plainText = null; + try { + final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, mReaderSecretKey, + new GCMParameterSpec(128, iv.array())); + plainText = cipher.doFinal(messageCiphertext); + } catch (BadPaddingException + | IllegalBlockSizeException + | InvalidAlgorithmParameterException + | InvalidKeyException + | NoSuchAlgorithmException + | NoSuchPaddingException e) { + throw new MessageDecryptionException("Error decrypting message", e); + } + mReadersExpectedEphemeralCounter += 1; + return plainText; + } + + @Override + public @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain() { + try { + byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(); + ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob); + + Collection<? extends Certificate> certs = null; + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + certs = factory.generateCertificates(bais); + } catch (CertificateException e) { + throw new RuntimeException("Error decoding certificates", e); + } + + LinkedList<X509Certificate> x509Certs = new LinkedList<>(); + for (Certificate cert : certs) { + x509Certs.add((X509Certificate) cert); + } + return x509Certs; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + private boolean mAllowUsingExhaustedKeys = true; + + @Override + public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) { + mAllowUsingExhaustedKeys = allowUsingExhaustedKeys; + } + + private boolean mOperationHandleSet = false; + private long mOperationHandle = 0; + + /** + * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an + * operation handle. + * + * @hide + */ + @Override + public long getCredstoreOperationHandle() { + if (!mOperationHandleSet) { + try { + mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys); + mOperationHandleSet = true; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) { + // The NoAuthenticationKeyAvailableException will be thrown when + // the caller proceeds to call getEntries(). + } + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + return mOperationHandle; + } + + @NonNull + @Override + public ResultData getEntries( + @Nullable byte[] requestMessage, + @NonNull Map<String, Collection<String>> entriesToRequest, + @Nullable byte[] sessionTranscript, + @Nullable byte[] readerSignature) + throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException, + InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException, + InvalidRequestMessageException { + + RequestNamespaceParcel[] rnsParcels = new RequestNamespaceParcel[entriesToRequest.size()]; + int n = 0; + for (String namespaceName : entriesToRequest.keySet()) { + Collection<String> entryNames = entriesToRequest.get(namespaceName); + rnsParcels[n] = new RequestNamespaceParcel(); + rnsParcels[n].namespaceName = namespaceName; + rnsParcels[n].entries = new RequestEntryParcel[entryNames.size()]; + int m = 0; + for (String entryName : entryNames) { + rnsParcels[n].entries[m] = new RequestEntryParcel(); + rnsParcels[n].entries[m].name = entryName; + m++; + } + n++; + } + + GetEntriesResultParcel resultParcel = null; + try { + resultParcel = mBinder.getEntries( + requestMessage != null ? requestMessage : new byte[0], + rnsParcels, + sessionTranscript != null ? sessionTranscript : new byte[0], + readerSignature != null ? readerSignature : new byte[0], + mAllowUsingExhaustedKeys); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND) { + throw new EphemeralPublicKeyNotFoundException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_INVALID_READER_SIGNATURE) { + throw new InvalidReaderSignatureException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) { + throw new NoAuthenticationKeyAvailableException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_INVALID_ITEMS_REQUEST_MESSAGE) { + throw new InvalidRequestMessageException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_SESSION_TRANSCRIPT_MISMATCH) { + throw new SessionTranscriptMismatchException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + byte[] mac = resultParcel.mac; + if (mac != null && mac.length == 0) { + mac = null; + } + CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder( + resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac); + + for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) { + for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) { + if (resultEntryParcel.status == ICredential.STATUS_OK) { + resultDataBuilder.addEntry(resultNamespaceParcel.namespaceName, + resultEntryParcel.name, resultEntryParcel.value); + } else { + resultDataBuilder.addErrorStatus(resultNamespaceParcel.namespaceName, + resultEntryParcel.name, + resultEntryParcel.status); + } + } + } + return resultDataBuilder.build(); + } + + @Override + public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) { + try { + mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override + public @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() { + try { + AuthKeyParcel[] authKeyParcels = mBinder.getAuthKeysNeedingCertification(); + LinkedList<X509Certificate> x509Certs = new LinkedList<>(); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + for (AuthKeyParcel authKeyParcel : authKeyParcels) { + Collection<? extends Certificate> certs = null; + ByteArrayInputStream bais = new ByteArrayInputStream(authKeyParcel.x509cert); + certs = factory.generateCertificates(bais); + if (certs.size() != 1) { + throw new RuntimeException("Returned blob yields more than one X509 cert"); + } + X509Certificate authKeyCert = (X509Certificate) certs.iterator().next(); + x509Certs.add(authKeyCert); + } + return x509Certs; + } catch (CertificateException e) { + throw new RuntimeException("Error decoding authenticationKey", e); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override + public void storeStaticAuthenticationData(X509Certificate authenticationKey, + byte[] staticAuthData) + throws UnknownAuthenticationKeyException { + try { + AuthKeyParcel authKeyParcel = new AuthKeyParcel(); + authKeyParcel.x509cert = authenticationKey.getEncoded(); + mBinder.storeStaticAuthenticationData(authKeyParcel, staticAuthData); + } catch (CertificateEncodingException e) { + throw new RuntimeException("Error encoding authenticationKey", e); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_AUTHENTICATION_KEY_NOT_FOUND) { + throw new UnknownAuthenticationKeyException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override + public @NonNull int[] getAuthenticationDataUsageCount() { + try { + int[] usageCount = mBinder.getAuthenticationDataUsageCount(); + return usageCount; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } +} diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java new file mode 100644 index 000000000000..dcc6b95aec02 --- /dev/null +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -0,0 +1,162 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.ServiceManager; + +class CredstoreIdentityCredentialStore extends IdentityCredentialStore { + + private static final String TAG = "CredstoreIdentityCredentialStore"; + + private Context mContext = null; + private ICredentialStore mStore = null; + + private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) { + mContext = context; + mStore = store; + } + + static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context, + int credentialStoreType) { + ICredentialStoreFactory storeFactory = + ICredentialStoreFactory.Stub.asInterface( + ServiceManager.getService("android.security.identity")); + + ICredentialStore credStore = null; + try { + credStore = storeFactory.getCredentialStore(credentialStoreType); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_GENERIC) { + return null; + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + if (credStore == null) { + return null; + } + + return new CredstoreIdentityCredentialStore(context, credStore); + } + + private static CredstoreIdentityCredentialStore sInstanceDefault = null; + private static CredstoreIdentityCredentialStore sInstanceDirectAccess = null; + + public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) { + if (sInstanceDefault == null) { + sInstanceDefault = getInstanceForType(context, + ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DEFAULT); + } + return sInstanceDefault; + } + + public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull + Context context) { + if (sInstanceDirectAccess == null) { + sInstanceDirectAccess = getInstanceForType(context, + ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DIRECT_ACCESS); + } + return sInstanceDirectAccess; + } + + @Override + public @NonNull String[] getSupportedDocTypes() { + try { + SecurityHardwareInfoParcel info; + info = mStore.getSecurityHardwareInfo(); + return info.supportedDocTypes; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override public @NonNull WritableIdentityCredential createCredential( + @NonNull String credentialName, + @NonNull String docType) throws AlreadyPersonalizedException, + DocTypeNotSupportedException { + try { + IWritableCredential wc; + wc = mStore.createCredential(credentialName, docType); + return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_ALREADY_PERSONALIZED) { + throw new AlreadyPersonalizedException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_DOCUMENT_TYPE_NOT_SUPPORTED) { + throw new DocTypeNotSupportedException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override public @Nullable IdentityCredential getCredentialByName( + @NonNull String credentialName, + @Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException { + try { + ICredential credstoreCredential; + credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite); + return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite, + credstoreCredential); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) { + return null; + } else if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) { + throw new CipherSuiteNotSupportedException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override + public @Nullable byte[] deleteCredentialByName(@NonNull String credentialName) { + ICredential credstoreCredential = null; + try { + try { + credstoreCredential = mStore.getCredentialByName(credentialName, + CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) { + return null; + } + } + byte[] proofOfDeletion = credstoreCredential.deleteCredential(); + return proofOfDeletion; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + +} diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java new file mode 100644 index 000000000000..ef7afca6b888 --- /dev/null +++ b/identity/java/android/security/identity/CredstoreResultData.java @@ -0,0 +1,162 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; + +/** + * An object that contains the result of retrieving data from a credential. This is used to return + * data requested from a {@link IdentityCredential}. + */ +class CredstoreResultData extends ResultData { + + byte[] mStaticAuthenticationData = null; + byte[] mAuthenticatedData = null; + byte[] mMessageAuthenticationCode = null; + + private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>(); + + private static class EntryData { + @Status + int mStatus; + byte[] mValue; + + EntryData(byte[] value, @Status int status) { + this.mValue = value; + this.mStatus = status; + } + } + + CredstoreResultData() {} + + @Override + public @NonNull byte[] getAuthenticatedData() { + return mAuthenticatedData; + } + + @Override + public @Nullable byte[] getMessageAuthenticationCode() { + return mMessageAuthenticationCode; + } + + @Override + public @NonNull byte[] getStaticAuthenticationData() { + return mStaticAuthenticationData; + } + + @Override + public @NonNull Collection<String> getNamespaceNames() { + return Collections.unmodifiableCollection(mData.keySet()); + } + + @Override + public @Nullable Collection<String> getEntryNames(@NonNull String namespaceName) { + Map<String, EntryData> innerMap = mData.get(namespaceName); + if (innerMap == null) { + return null; + } + return Collections.unmodifiableCollection(innerMap.keySet()); + } + + @Override + public @Nullable Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) { + Map<String, EntryData> innerMap = mData.get(namespaceName); + if (innerMap == null) { + return null; + } + LinkedList<String> result = new LinkedList<String>(); + for (Map.Entry<String, EntryData> entry : innerMap.entrySet()) { + if (entry.getValue().mStatus == STATUS_OK) { + result.add(entry.getKey()); + } + } + return result; + } + + private EntryData getEntryData(@NonNull String namespaceName, @NonNull String name) { + Map<String, EntryData> innerMap = mData.get(namespaceName); + if (innerMap == null) { + return null; + } + return innerMap.get(name); + } + + @Override + @Status + public int getStatus(@NonNull String namespaceName, @NonNull String name) { + EntryData value = getEntryData(namespaceName, name); + if (value == null) { + return STATUS_NOT_REQUESTED; + } + return value.mStatus; + } + + @Override + public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) { + EntryData value = getEntryData(namespaceName, name); + if (value == null) { + return null; + } + return value.mValue; + } + + static class Builder { + private CredstoreResultData mResultData; + + Builder(byte[] staticAuthenticationData, + byte[] authenticatedData, + byte[] messageAuthenticationCode) { + this.mResultData = new CredstoreResultData(); + this.mResultData.mStaticAuthenticationData = staticAuthenticationData; + this.mResultData.mAuthenticatedData = authenticatedData; + this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode; + } + + private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) { + Map<String, EntryData> innerMap = mResultData.mData.get(namespaceName); + if (innerMap == null) { + innerMap = new LinkedHashMap<>(); + mResultData.mData.put(namespaceName, innerMap); + } + return innerMap; + } + + Builder addEntry(String namespaceName, String name, byte[] value) { + Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName); + innerMap.put(name, new EntryData(value, STATUS_OK)); + return this; + } + + Builder addErrorStatus(String namespaceName, String name, @Status int status) { + Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName); + innerMap.put(name, new EntryData(null, status)); + return this; + } + + CredstoreResultData build() { + return mResultData; + } + } + +} diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java new file mode 100644 index 000000000000..335636cb07ae --- /dev/null +++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java @@ -0,0 +1,168 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; +import android.content.Context; +import android.security.GateKeeper; + +import java.io.ByteArrayInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.LinkedList; + +class CredstoreWritableIdentityCredential extends WritableIdentityCredential { + + private static final String TAG = "CredstoreWritableIdentityCredential"; + + private String mDocType; + private String mCredentialName; + private Context mContext; + private IWritableCredential mBinder; + + CredstoreWritableIdentityCredential(Context context, + @NonNull String credentialName, + @NonNull String docType, + IWritableCredential binder) { + mContext = context; + mDocType = docType; + mCredentialName = credentialName; + mBinder = binder; + } + + @NonNull @Override + public Collection<X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[] challenge) { + try { + byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge); + ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob); + + Collection<? extends Certificate> certs = null; + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + certs = factory.generateCertificates(bais); + } catch (CertificateException e) { + throw new RuntimeException("Error decoding certificates", e); + } + + LinkedList<X509Certificate> x509Certs = new LinkedList<>(); + for (Certificate cert : certs) { + x509Certs.add((X509Certificate) cert); + } + return x509Certs; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @NonNull @Override + public byte[] personalize(@NonNull PersonalizationData personalizationData) { + + Collection<AccessControlProfile> accessControlProfiles = + personalizationData.getAccessControlProfiles(); + + AccessControlProfileParcel[] acpParcels = + new AccessControlProfileParcel[accessControlProfiles.size()]; + boolean usingUserAuthentication = false; + int n = 0; + for (AccessControlProfile profile : accessControlProfiles) { + acpParcels[n] = new AccessControlProfileParcel(); + acpParcels[n].id = profile.getAccessControlProfileId().getId(); + X509Certificate cert = profile.getReaderCertificate(); + if (cert != null) { + try { + acpParcels[n].readerCertificate = cert.getEncoded(); + } catch (CertificateException e) { + throw new RuntimeException("Error encoding reader certificate", e); + } + } else { + acpParcels[n].readerCertificate = new byte[0]; + } + acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired(); + acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout(); + if (profile.isUserAuthenticationRequired()) { + usingUserAuthentication = true; + } + n++; + } + + Collection<String> namespaceNames = personalizationData.getNamespaceNames(); + + EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaceNames.size()]; + n = 0; + for (String namespaceName : namespaceNames) { + PersonalizationData.NamespaceData nsd = + personalizationData.getNamespaceData(namespaceName); + + ensParcels[n] = new EntryNamespaceParcel(); + ensParcels[n].namespaceName = namespaceName; + + Collection<String> entryNames = nsd.getEntryNames(); + EntryParcel[] eParcels = new EntryParcel[entryNames.size()]; + int m = 0; + for (String entryName : entryNames) { + eParcels[m] = new EntryParcel(); + eParcels[m].name = entryName; + eParcels[m].value = nsd.getEntryValue(entryName); + Collection<AccessControlProfileId> acpIds = + nsd.getAccessControlProfileIds(entryName); + eParcels[m].accessControlProfileIds = new int[acpIds.size()]; + int o = 0; + for (AccessControlProfileId acpId : acpIds) { + eParcels[m].accessControlProfileIds[o++] = acpId.getId(); + } + m++; + } + ensParcels[n].entries = eParcels; + n++; + } + + // Note: The value 0 is used to convey that no user-authentication is needed for this + // credential. This is to allow creating credentials w/o user authentication on devices + // where Secure lock screen is not enabled. + long secureUserId = 0; + if (usingUserAuthentication) { + secureUserId = getRootSid(); + } + try { + byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels, + secureUserId); + return personalizationReceipt; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + private static long getRootSid() { + long rootSid = GateKeeper.getSecureUserId(); + if (rootSid == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to create credentials requiring user authentication"); + } + return rootSid; + } + + +} diff --git a/identity/java/android/security/identity/DocTypeNotSupportedException.java b/identity/java/android/security/identity/DocTypeNotSupportedException.java new file mode 100644 index 000000000000..754e44af5411 --- /dev/null +++ b/identity/java/android/security/identity/DocTypeNotSupportedException.java @@ -0,0 +1,43 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to create a credential with an unsupported document type. + */ +public class DocTypeNotSupportedException extends IdentityCredentialException { + /** + * Constructs a new {@link DocTypeNotSupportedException} exception. + * + * @param message the detail message. + */ + public DocTypeNotSupportedException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link DocTypeNotSupportedException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public DocTypeNotSupportedException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java new file mode 100644 index 000000000000..265f271b54f7 --- /dev/null +++ b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java @@ -0,0 +1,44 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if the ephemeral public key was not found in the session transcript + * passed to {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}. + */ +public class EphemeralPublicKeyNotFoundException extends IdentityCredentialException { + /** + * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception. + * + * @param message the detail message. + */ + public EphemeralPublicKeyNotFoundException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public EphemeralPublicKeyNotFoundException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java new file mode 100644 index 000000000000..bd439199f914 --- /dev/null +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -0,0 +1,309 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Map; + +/** + * Class used to read data from a previously provisioned credential. + * + * Use {@link IdentityCredentialStore#getCredentialByName(String, int)} to get a + * {@link IdentityCredential} instance. + */ +public abstract class IdentityCredential { + /** + * @hide + */ + protected IdentityCredential() {} + + /** + * Create an ephemeral key pair to use to establish a secure channel with a reader. + * + * <p>Most applications will use only the public key, and only to send it to the reader, + * allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])} + * and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for + * applications that wish to use a cipher suite that is not supported by + * {@link IdentityCredentialStore}. + * + * @return ephemeral key pair to use to establish a secure channel with a reader. + */ + public @NonNull abstract KeyPair createEphemeralKeyPair(); + + /** + * Set the ephemeral public key provided by the reader. This must be called before + * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called. + * + * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to + * establish a secure session. + * @throws InvalidKeyException if the given key is invalid. + */ + public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) + throws InvalidKeyException; + + /** + * Encrypt a message for transmission to the reader. + * + * @param messagePlaintext unencrypted message to encrypt. + * @return encrypted message. + */ + public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext); + + /** + * Decrypt a message received from the reader. + * + * @param messageCiphertext encrypted message to decrypt. + * @return decrypted message. + * @throws MessageDecryptionException if the ciphertext couldn't be decrypted. + */ + public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext) + throws MessageDecryptionException; + + /** + * Gets the X.509 certificate chain for the CredentialKey which identifies this + * credential to the issuing authority. This is the same certificate chain that + * was returned by {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])} + * when the credential was first created and its Android Keystore extension will + * contain the <code>challenge</code> data set at that time. See the documentation + * for that method for important information about this certificate chain. + * + * @return the certificate chain for this credential's CredentialKey. + */ + public @NonNull abstract Collection<X509Certificate> getCredentialKeyCertificateChain(); + + /** + * Sets whether to allow using an authentication key which use count has been exceeded if no + * other key is available. This must be called prior to calling + * {@link #getEntries(byte[], Map, byte[], byte[])} or using a + * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this + * object. + * + * By default this is set to true. + * + * @param allowUsingExhaustedKeys whether to allow using an authentication key which use count + * has been exceeded if no other key is available. + */ + public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys); + + /** + * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an + * operation handle. + * + * @hide + */ + public abstract long getCredstoreOperationHandle(); + + /** + * Retrieve data entries and associated data from this {@code IdentityCredential}. + * + * <p>If an access control check fails for one of the requested entries or if the entry + * doesn't exist, the entry is simply not returned. The application can detect this + * by using the {@link ResultData#getStatus(String, String)} method on each of the requested + * entries. + * + * <p>It is the responsibility of the calling application to know if authentication is needed + * and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user + * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which + * references this object. If needed, this must be done before calling + * {@link #getEntries(byte[], Map, byte[], byte[])}. + * + * <p>If this method returns successfully (i.e. without throwing an exception), it must not be + * called again on this instance. + * + * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request + * from the verifier. The content can be defined in the way appropriate for the credential, byt + * there are three requirements that must be met to work with this API: + * <ul> + * <li>The content must be a CBOR-encoded structure.</li> + * <li>The CBOR structure must be a map.</li> + * <li>The map must contain a tstr key "nameSpaces" whose value contains a map, as described in + * the example below.</li> + * </ul> + * + * <p>Here's an example of CBOR which conforms to this requirement: + * <pre> + * ItemsRequest = { + * ? "docType" : DocType, + * "nameSpaces" : NameSpaces, + * ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide + * } + * + * NameSpaces = { + * + NameSpace => DataElements ; Requested data elements for each NameSpace + * } + * + * NameSpace = tstr + * + * DataElements = { + * + DataElement => IntentToRetain + * } + * + * DataElement = tstr + * IntentToRetain = bool + * </pre> + * + * <p>If the {@code sessionTranscript} parameter is not {@code null}, it must contain CBOR + * data conforming to the following CDDL schema: + * + * <pre> + * SessionTranscript = [ + * DeviceEngagementBytes, + * EReaderKeyBytes + * ] + * + * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) + * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) + * </pre> + * + * <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part + * of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear + * somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present + * in uncompressed form. + * + * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1 + * structure as defined in RFC 8152. For the payload nil shall be used and the + * detached payload is the ReaderAuthentication CBOR described below. + * <pre> + * ReaderAuthentication = [ + * "ReaderAuthentication", + * SessionTranscript, + * ItemsRequestBytes + * ] + * + * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) ; Bytes of ItemsRequest + * </pre> + * + * <p>The public key corresponding to the key used to made signature, can be + * found in the {@code x5chain} unprotected header element of the COSE_Sign1 + * structure (as as described in 'draft-ietf-cose-x509-04'). There will be at + * least one certificate in said element and there may be more (and if so, + * each certificate must be signed by its successor). + * + * <p>Data elements protected by reader authentication is returned if, and only if, they are + * mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most + * certificate in {@code readerCertificateChain}, and the data element is configured + * with an {@link AccessControlProfile} with a {@link X509Certificate} in + * {@code readerCertificateChain}. + * + * <p>Note that only items referenced in {@code entriesToRequest} are returned - the + * {@code requestMessage} parameter is only used to for enforcing reader authentication. + * + * @param requestMessage If not {@code null}, must contain CBOR data conforming to + * the schema mentioned above. + * @param entriesToRequest The entries to request, organized as a map of namespace + * names with each value being a collection of data elements + * in the given namespace. + * @param readerSignature COSE_Sign1 structure as described above or {@code null} + * if reader authentication is not being used. + * @return A {@link ResultData} object containing entry data organized by namespace and a + * cryptographically authenticated representation of the same data. + * @throws SessionTranscriptMismatchException Thrown when trying use multiple different + * session transcripts in the same presentation + * session. + * @throws NoAuthenticationKeyAvailableException if authentication keys were never + * provisioned, the method + * {@link #setAvailableAuthenticationKeys(int, int)} + * was called with {@code keyCount} set to 0, + * the method + * {@link #setAllowUsingExhaustedKeys(boolean)} + * was called with {@code false} and all + * available authentication keys have been + * exhausted. + * @throws InvalidReaderSignatureException if the reader signature is invalid, or it + * doesn't contain a certificate chain, or if + * the signature failed to validate. + * @throws InvalidRequestMessageException if the requestMessage is malformed. + * @throws EphemeralPublicKeyNotFoundException if the ephemeral public key was not found in + * the session transcript. + */ + public abstract @NonNull ResultData getEntries( + @Nullable byte[] requestMessage, + @NonNull Map<String, Collection<String>> entriesToRequest, + @Nullable byte[] sessionTranscript, + @Nullable byte[] readerSignature) + throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException, + InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException, + InvalidRequestMessageException; + + /** + * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain, + * and the number of times each should be used. + * + * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each + * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s + * for which this method has not been called behave as though it had been called wit + * {@code keyCount} 0 and {@code maxUsesPerKey} 1. + * + * @param keyCount The number of active, certified dynamic authentication keys the + * {@code IdentityCredential} will try to keep available. This value + * must be non-negative. + * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's + * eligible for replacement. This value must be greater than zero. + */ + public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey); + + /** + * Gets a collection of dynamic authentication keys that need certification. + * + * <p>When there aren't enough certified dynamic authentication keys, either because the key + * count has been increased or because one or more keys have reached their usage count, this + * method will generate replacement keys and certificates and return them for issuer + * certification. The issuer certificates and associated static authentication data must then + * be provided back to the {@code IdentityCredential} using + * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}. + * + * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey + * can be obtained using the {@link #getCredentialKeyCertificateChain()} method. + * + * @return A collection of X.509 certificates for dynamic authentication keys that need issuer + * certification. + */ + public @NonNull abstract Collection<X509Certificate> getAuthKeysNeedingCertification(); + + /** + * Store authentication data associated with a dynamic authentication key. + * + * This should only be called for an authenticated key returned by + * {@link #getAuthKeysNeedingCertification()}. + * + * @param authenticationKey The dynamic authentication key for which certification and + * associated static + * authentication data is being provided. + * @param staticAuthData Static authentication data provided by the issuer that validates + * the authenticity + * and integrity of the credential data fields. + * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized. + */ + public abstract void storeStaticAuthenticationData( + @NonNull X509Certificate authenticationKey, + @NonNull byte[] staticAuthData) + throws UnknownAuthenticationKeyException; + + /** + * Get the number of times the dynamic authentication keys have been used. + * + * @return int array of dynamic authentication key usage counts. + */ + public @NonNull abstract int[] getAuthenticationDataUsageCount(); +} diff --git a/identity/java/android/security/identity/IdentityCredentialException.java b/identity/java/android/security/identity/IdentityCredentialException.java new file mode 100644 index 000000000000..c8113803d5d3 --- /dev/null +++ b/identity/java/android/security/identity/IdentityCredentialException.java @@ -0,0 +1,44 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Base class for all Identity Credential exceptions. + */ +public class IdentityCredentialException extends Exception { + /** + * Constructs a new {@link IdentityCredentialException} exception. + * + * @param message the detail message. + */ + public IdentityCredentialException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link IdentityCredentialException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public IdentityCredentialException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } + +} diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java new file mode 100644 index 000000000000..a1dfc77adb29 --- /dev/null +++ b/identity/java/android/security/identity/IdentityCredentialStore.java @@ -0,0 +1,186 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An interface to a secure store for user identity documents. + * + * <p>This interface is deliberately fairly general and abstract. To the extent possible, + * specification of the message formats and semantics of communication with credential + * verification devices and issuing authorities (IAs) is out of scope. It provides the + * interface with secure storage but a credential-specific Android application will be + * required to implement the presentation and verification protocols and processes + * appropriate for the specific credential type. + * + * <p>Multiple credentials can be created. Each credential comprises:</p> + * <ul> + * <li>A document type, which is a string.</li> + * + * <li>A set of namespaces, which serve to disambiguate value names. It is recommended + * that namespaces be structured as reverse domain names so that IANA effectively serves + * as the namespace registrar.</li> + * + * <li>For each namespace, a set of name/value pairs, each with an associated set of + * access control profile IDs. Names are strings and values are typed and can be any + * value supported by <a href="http://cbor.io/">CBOR</a>.</li> + * + * <li>A set of access control profiles, each with a profile ID and a specification + * of the conditions which satisfy the profile's requirements.</li> + * + * <li>An asymmetric key pair which is used to authenticate the credential to the Issuing + * Authority, called the <em>CredentialKey</em>.</li> + * + * <li>A set of zero or more named reader authentication public keys, which are used to + * authenticate an authorized reader to the credential.</li> + * + * <li>A set of named signing keys, which are used to sign collections of values and session + * transcripts.</li> + * </ul> + * + * <p>Implementing support for user identity documents in secure storage requires dedicated + * hardware-backed support and may not always be available. + * + * <p>Two different credential stores exist - the <em>default</em> store and the + * <em>direct access</em> store. Most often credentials will be accessed through the default + * store but that requires that the Android device be powered up and fully functional. + * It is desirable to allow identity credential usage when the Android device's battery is too + * low to boot the Android operating system, so direct access to the secure hardware via NFC + * may allow data retrieval, if the secure hardware chooses to implement it. + * + * <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader + * authentication to protect data elements. The reason for this is user authentication or user + * approval of data release is not possible when the device is off. + */ +public abstract class IdentityCredentialStore { + IdentityCredentialStore() {} + + /** + * Specifies that the cipher suite that will be used to secure communications between the reader + * is: + * + * <ul> + * <li>ECDHE with HKDF-SHA-256 for key agreement.</li> + * <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one + * for every message).</li> + * <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat + * man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential} + * for details on reader and prover signing keys.</li> + * </ul> + * + * <p> + * At present this is the only supported cipher suite. + */ + public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; + + /** + * Gets the default {@link IdentityCredentialStore}. + * + * @param context the application context. + * @return the {@link IdentityCredentialStore} or {@code null} if the device doesn't + * have hardware-backed support for secure storage of user identity documents. + */ + public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) { + return CredstoreIdentityCredentialStore.getInstance(context); + } + + /** + * Gets the {@link IdentityCredentialStore} for direct access. + * + * <p>Direct access requires specialized NFC hardware and may not be supported on all + * devices even if default store is available. Credentials provisioned to the direct + * access store should <strong>always</strong> use reader authentication to protect + * data elements. + * + * @param context the application context. + * @return the {@link IdentityCredentialStore} or {@code null} if direct access is not + * supported on this device. + */ + public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull + Context context) { + return CredstoreIdentityCredentialStore.getDirectAccessInstance(context); + } + + /** + * Gets a list of supported document types. + * + * <p>Only the direct-access store may restrict the kind of document types that can be used for + * credentials. The default store always supports any document type. + * + * @return The supported document types or the empty array if any document type is supported. + */ + public abstract @NonNull String[] getSupportedDocTypes(); + + /** + * Creates a new credential. + * + * @param credentialName The name used to identify the credential. + * @param docType The document type for the credential. + * @return A @{link WritableIdentityCredential} that can be used to create a new credential. + * @throws AlreadyPersonalizedException if a credential with the given name already exists. + * @throws DocTypeNotSupportedException if the given document type isn't supported by the store. + */ + public abstract @NonNull WritableIdentityCredential createCredential( + @NonNull String credentialName, @NonNull String docType) + throws AlreadyPersonalizedException, DocTypeNotSupportedException; + + /** + * Retrieve a named credential. + * + * @param credentialName the name of the credential to retrieve. + * @param cipherSuite the cipher suite to use for communicating with the verifier. + * @return The named credential, or null if not found. + */ + public abstract @Nullable IdentityCredential getCredentialByName(@NonNull String credentialName, + @Ciphersuite int cipherSuite) + throws CipherSuiteNotSupportedException; + + /** + * Delete a named credential. + * + * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey + * with payload set to {@code ProofOfDeletion} as defined below: + * + * <pre> + * ProofOfDeletion = [ + * "ProofOfDeletion", ; tstr + * tstr, ; DocType + * bool ; true if this is a test credential, should + * ; always be false. + * ] + * </pre> + * + * @param credentialName the name of the credential to delete. + * @return {@code null} if the credential was not found, the COSE_Sign1 data structure above + * if the credential was found and deleted. + */ + public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName); + + /** @hide */ + @IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256}) + @Retention(RetentionPolicy.SOURCE) + public @interface Ciphersuite { + } + +} diff --git a/identity/java/android/security/identity/InvalidReaderSignatureException.java b/identity/java/android/security/identity/InvalidReaderSignatureException.java new file mode 100644 index 000000000000..3f7027005830 --- /dev/null +++ b/identity/java/android/security/identity/InvalidReaderSignatureException.java @@ -0,0 +1,46 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if the reader signature is invalid, or it doesn't contain a certificate chain, or if the + * signature failed to validate. + */ +public class InvalidReaderSignatureException extends IdentityCredentialException { + /** + * Constructs a new {@link InvalidReaderSignatureException} exception. + * + * @param message the detail message. + */ + public InvalidReaderSignatureException(@NonNull String message) { + super(message); + } + + + /** + * Constructs a new {@link InvalidReaderSignatureException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public InvalidReaderSignatureException(@NonNull String message, + @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/InvalidRequestMessageException.java b/identity/java/android/security/identity/InvalidRequestMessageException.java new file mode 100644 index 000000000000..b0c073c33a8d --- /dev/null +++ b/identity/java/android/security/identity/InvalidRequestMessageException.java @@ -0,0 +1,46 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if message with the request doesn't satisfy the requirements documented in + * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}. + */ +public class InvalidRequestMessageException extends IdentityCredentialException { + /** + * Constructs a new {@link InvalidRequestMessageException} exception. + * + * @param message the detail message. + */ + public InvalidRequestMessageException(@NonNull String message) { + super(message); + } + + + /** + * Constructs a new {@link InvalidRequestMessageException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public InvalidRequestMessageException(@NonNull String message, + @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/MessageDecryptionException.java b/identity/java/android/security/identity/MessageDecryptionException.java new file mode 100644 index 000000000000..7a6169ea1d5b --- /dev/null +++ b/identity/java/android/security/identity/MessageDecryptionException.java @@ -0,0 +1,44 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown when failing to decrypt a message from the reader device. + */ +public class MessageDecryptionException extends IdentityCredentialException { + + /** + * Constructs a new {@link MessageDecryptionException} exception. + * + * @param message the detail message. + */ + public MessageDecryptionException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link MessageDecryptionException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public MessageDecryptionException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java new file mode 100644 index 000000000000..7f404037fbb1 --- /dev/null +++ b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java @@ -0,0 +1,46 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if no dynamic authentication keys are available. + */ +public class NoAuthenticationKeyAvailableException extends IdentityCredentialException { + + /** + * Constructs a new {@link NoAuthenticationKeyAvailableException} exception. + * + * @param message the detail message. + */ + public NoAuthenticationKeyAvailableException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link NoAuthenticationKeyAvailableException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public NoAuthenticationKeyAvailableException(@NonNull String message, + @NonNull Throwable cause) { + super(message, cause); + } + +} diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java new file mode 100644 index 000000000000..44370a1780f8 --- /dev/null +++ b/identity/java/android/security/identity/PersonalizationData.java @@ -0,0 +1,157 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; + +/** + * An object that holds personalization data. + * + * This data includes access control profiles and a set of data entries and values, grouped by + * namespace. + * + * This is used to provision data into a {@link WritableIdentityCredential}. + * + * @see WritableIdentityCredential#personalize + */ +public class PersonalizationData { + + private PersonalizationData() { + } + + private LinkedList<AccessControlProfile> mProfiles = new LinkedList<>(); + + private LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>(); + + Collection<AccessControlProfile> getAccessControlProfiles() { + return Collections.unmodifiableCollection(mProfiles); + } + + Collection<String> getNamespaceNames() { + return Collections.unmodifiableCollection(mNamespaces.keySet()); + } + + NamespaceData getNamespaceData(String namespace) { + return mNamespaces.get(namespace); + } + + static class NamespaceData { + + private String mNamespace; + private LinkedHashMap<String, EntryData> mEntries = new LinkedHashMap<>(); + + private NamespaceData(String namespace) { + this.mNamespace = namespace; + } + + String getNamespaceName() { + return mNamespace; + } + + Collection<String> getEntryNames() { + return Collections.unmodifiableCollection(mEntries.keySet()); + } + + Collection<AccessControlProfileId> getAccessControlProfileIds(String name) { + EntryData value = mEntries.get(name); + if (value != null) { + return value.mAccessControlProfileIds; + } + return null; + } + + byte[] getEntryValue(String name) { + EntryData value = mEntries.get(name); + if (value != null) { + return value.mValue; + } + return null; + } + } + + private static class EntryData { + byte[] mValue; + Collection<AccessControlProfileId> mAccessControlProfileIds; + + EntryData(byte[] value, Collection<AccessControlProfileId> accessControlProfileIds) { + this.mValue = value; + this.mAccessControlProfileIds = accessControlProfileIds; + } + } + + /** + * A builder for {@link PersonalizationData}. + */ + public static final class Builder { + private PersonalizationData mData; + + /** + * Creates a new builder for a given namespace. + */ + public Builder() { + this.mData = new PersonalizationData(); + } + + /** + * Adds a new entry to the builder. + * + * @param namespace The namespace to use, e.g. {@code org.iso.18013-5.2019}. + * @param name The name of the entry, e.g. {@code height}. + * @param accessControlProfileIds A set of access control profiles to use. + * @param value The value to add, in CBOR encoding. + * @return The builder. + */ + public @NonNull Builder setEntry(@NonNull String namespace, @NonNull String name, + @NonNull Collection<AccessControlProfileId> accessControlProfileIds, + @NonNull byte[] value) { + NamespaceData namespaceData = mData.mNamespaces.get(namespace); + if (namespaceData == null) { + namespaceData = new NamespaceData(namespace); + mData.mNamespaces.put(namespace, namespaceData); + } + // TODO: validate/verify that value is proper CBOR. + namespaceData.mEntries.put(name, new EntryData(value, accessControlProfileIds)); + return this; + } + + /** + * Adds a new access control profile to the builder. + * + * @param profile The access control profile. + * @return The builder. + */ + public @NonNull Builder addAccessControlProfile(@NonNull AccessControlProfile profile) { + mData.mProfiles.add(profile); + return this; + } + + /** + * Creates a new {@link PersonalizationData} with all the entries added to the builder. + * + * @return A new {@link PersonalizationData} instance. + */ + public @NonNull PersonalizationData build() { + return mData; + } + } + +} diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java new file mode 100644 index 000000000000..0982c8a4ab31 --- /dev/null +++ b/identity/java/android/security/identity/ResultData.java @@ -0,0 +1,224 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.util.Collection; + +/** + * An object that contains the result of retrieving data from a credential. This is used to return + * data requested from a {@link IdentityCredential}. + */ +public abstract class ResultData { + + /** Value was successfully retrieved. */ + public static final int STATUS_OK = 0; + + /** Requested entry does not exist. */ + public static final int STATUS_NO_SUCH_ENTRY = 1; + + /** Requested entry was not requested. */ + public static final int STATUS_NOT_REQUESTED = 2; + + /** Requested entry wasn't in the request message. */ + public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; + + /** The requested entry was not retrieved because user authentication wasn't performed. */ + public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; + + /** The requested entry was not retrieved because reader authentication wasn't performed. */ + public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; + + /** + * The requested entry was not retrieved because it was configured without any access + * control profile. + */ + public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; + + /** + * @hide + */ + protected ResultData() {} + + /** + * Returns a CBOR structure containing the retrieved data. + * + * <p>This structure - along with the session transcript - may be cryptographically + * authenticated to prove to the reader that the data is from a trusted credential and + * {@link #getMessageAuthenticationCode()} can be used to get a MAC. + * + * <p>The CBOR structure which is cryptographically authenticated is the + * {@code DeviceAuthentication} structure according to the following + * <a href="https://tools.ietf.org/html/draft-ietf-cbor-cddl-06">CDDL</a> schema: + * + * <pre> + * DeviceAuthentication = [ + * "DeviceAuthentication", + * SessionTranscript, + * DocType, + * DeviceNameSpacesBytes + * ] + * + * DocType = tstr + * + * SessionTranscript = [ + * DeviceEngagementBytes, + * EReaderKeyBytes + * ] + * + * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) + * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) + * + * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) + * </pre> + * + * where + * + * <pre> + * DeviceNameSpaces = { + * * NameSpace => DeviceSignedItems + * } + * + * DeviceSignedItems = { + * + DataItemName => DataItemValue + * } + * + * NameSpace = tstr + * DataItemName = tstr + * DataItemValue = any + * </pre> + * + * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure + * as defined above. + * + * @return The bytes of the {@code DeviceNameSpaces} CBOR structure. + */ + public abstract @NonNull byte[] getAuthenticatedData(); + + /** + * Returns a message authentication code over the data returned by + * {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted + * credential. + * + * <p>The MAC proves to the reader that the data is from a trusted credential. This code is + * produced by using the key agreement and key derivation function from the ciphersuite + * with the authentication private key and the reader ephemeral public key to compute a + * shared message authentication code (MAC) key, then using the MAC function from the + * ciphersuite to compute a MAC of the authenticated data. + * + * <p>If the {@code sessionTranscript} parameter passed to + * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null} + * or the reader ephmeral public key was never set using + * {@link IdentityCredential#setReaderEphemeralPublicKey(PublicKey)}, no message + * authencation code will be produced and this method will return {@code null}. + * + * @return A COSE_Mac0 structure with the message authentication code as described above + * or {@code null} if the conditions specified above are not met. + */ + public abstract @Nullable byte[] getMessageAuthenticationCode(); + + /** + * Returns the static authentication data associated with the dynamic authentication + * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}. + * + * @return The static authentication data associated with dynamic authentication key used to + * MAC the data. + */ + public abstract @NonNull byte[] getStaticAuthenticationData(); + + /** + * Gets the names of namespaces with retrieved entries. + * + * @return collection of name of namespaces containing retrieved entries. May be empty if no + * data was retrieved. + */ + public abstract @NonNull Collection<String> getNamespaceNames(); + + /** + * Get the names of all entries. + * + * This includes the name of entries that wasn't successfully retrieved. + * + * @param namespaceName the namespace name to get entries for. + * @return A collection of names or {@code null} if there are no entries for the given + * namespace. + */ + public abstract @Nullable Collection<String> getEntryNames(@NonNull String namespaceName); + + /** + * Get the names of all entries that was successfully retrieved. + * + * This only return entries for which {@link #getStatus(String, String)} will return + * {@link #STATUS_OK}. + * + * @param namespaceName the namespace name to get entries for. + * @return A collection of names or {@code null} if there are no entries for the given + * namespace. + */ + public abstract @Nullable Collection<String> getRetrievedEntryNames( + @NonNull String namespaceName); + + /** + * Gets the status of an entry. + * + * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY} + * if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested, + * {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't + * present in the request message, + * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value + * wasn't retrieved because the necessary user authentication wasn't performed, + * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain + * didn't match the set of certificates the entry was provisioned with, or + * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any + * access control profiles. + * + * @param namespaceName the namespace name of the entry. + * @param name the name of the entry to get the value for. + * @return the status indicating whether the value was retrieved and if not, why. + */ + @Status + public abstract int getStatus(@NonNull String namespaceName, @NonNull String name); + + /** + * Gets the raw CBOR data for the value of an entry. + * + * This should only be called on an entry for which the {@link #getStatus(String, String)} + * method returns {@link #STATUS_OK}. + * + * @param namespaceName the namespace name of the entry. + * @param name the name of the entry to get the value for. + * @return the raw CBOR data or {@code null} if no entry with the given name exists. + */ + public abstract @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name); + + /** + * The type of the entry status. + * @hide + */ + @Retention(SOURCE) + @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED, STATUS_NOT_IN_REQUEST_MESSAGE, + STATUS_USER_AUTHENTICATION_FAILED, STATUS_READER_AUTHENTICATION_FAILED, + STATUS_NO_ACCESS_CONTROL_PROFILES}) + public @interface Status { + } +} diff --git a/identity/java/android/security/identity/SessionTranscriptMismatchException.java b/identity/java/android/security/identity/SessionTranscriptMismatchException.java new file mode 100644 index 000000000000..8c2406043f67 --- /dev/null +++ b/identity/java/android/security/identity/SessionTranscriptMismatchException.java @@ -0,0 +1,44 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown when trying use multiple different session transcripts in the same presentation session. + */ +public class SessionTranscriptMismatchException extends IdentityCredentialException { + + /** + * Constructs a new {@link SessionTranscriptMismatchException} exception. + * + * @param message the detail message. + */ + public SessionTranscriptMismatchException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link SessionTranscriptMismatchException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public SessionTranscriptMismatchException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/UnknownAuthenticationKeyException.java b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java new file mode 100644 index 000000000000..f454b2ce2af9 --- /dev/null +++ b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java @@ -0,0 +1,43 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to certify an unknown dynamic authentication key. + */ +public class UnknownAuthenticationKeyException extends IdentityCredentialException { + /** + * Constructs a new {@link UnknownAuthenticationKeyException} exception. + * + * @param message the detail message. + */ + public UnknownAuthenticationKeyException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link UnknownAuthenticationKeyException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public UnknownAuthenticationKeyException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/Util.java b/identity/java/android/security/identity/Util.java new file mode 100644 index 000000000000..6eefeb8f3f2a --- /dev/null +++ b/identity/java/android/security/identity/Util.java @@ -0,0 +1,140 @@ +/* + * Copyright 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.security.identity; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECPoint; +import java.util.Collection; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +class Util { + private static final String TAG = "Util"; + + static int[] integerCollectionToArray(Collection<Integer> collection) { + int[] result = new int[collection.size()]; + int n = 0; + for (int item : collection) { + result[n++] = item; + } + return result; + } + + static byte[] stripLeadingZeroes(byte[] value) { + int n = 0; + while (n < value.length && value[n] == 0) { + n++; + } + int newLen = value.length - n; + byte[] ret = new byte[newLen]; + int m = 0; + while (n < value.length) { + ret[m++] = value[n++]; + } + return ret; + } + + static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) { + ECPoint w = ((ECPublicKey) publicKey).getW(); + // X and Y are always positive so for interop we remove any leading zeroes + // inserted by the BigInteger encoder. + byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); + byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(0x04); + baos.write(x); + baos.write(y); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Unexpected IOException", e); + } + } + + /** + * Computes an HKDF. + * + * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google + * /crypto/tink/subtle/Hkdf.java + * which is also Copyright (c) Google and also licensed under the Apache 2 license. + * + * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or + * "HMACSHA256". + * @param ikm the input keying material. + * @param salt optional salt. A possibly non-secret random value. If no salt is + * provided (i.e. if + * salt has length 0) then an array of 0s of the same size as the hash + * digest is used as salt. + * @param info optional context and application specific information. + * @param size The length of the generated pseudorandom string in bytes. The maximal + * size is + * 255.DigestSize, where DigestSize is the size of the underlying HMAC. + * @return size pseudorandom bytes. + */ + static byte[] computeHkdf( + String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) { + Mac mac = null; + try { + mac = Mac.getInstance(macAlgorithm); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("No such algorithm: " + macAlgorithm, e); + } + if (size > 255 * mac.getMacLength()) { + throw new RuntimeException("size too large"); + } + try { + if (salt == null || salt.length == 0) { + // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided + // then HKDF uses a salt that is an array of zeros of the same length as the hash + // digest. + mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm)); + } else { + mac.init(new SecretKeySpec(salt, macAlgorithm)); + } + byte[] prk = mac.doFinal(ikm); + byte[] result = new byte[size]; + int ctr = 1; + int pos = 0; + mac.init(new SecretKeySpec(prk, macAlgorithm)); + byte[] digest = new byte[0]; + while (true) { + mac.update(digest); + mac.update(info); + mac.update((byte) ctr); + digest = mac.doFinal(); + if (pos + digest.length < size) { + System.arraycopy(digest, 0, result, pos, digest.length); + pos += digest.length; + ctr++; + } else { + System.arraycopy(digest, 0, result, pos, size - pos); + break; + } + } + return result; + } catch (InvalidKeyException e) { + throw new RuntimeException("Error MACing", e); + } + } + +} diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java new file mode 100644 index 000000000000..5f575b9d56f3 --- /dev/null +++ b/identity/java/android/security/identity/WritableIdentityCredential.java @@ -0,0 +1,111 @@ +/* + * Copyright 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.security.identity; + +import android.annotation.NonNull; + +import java.security.cert.X509Certificate; +import java.util.Collection; + +/** + * Class used to personalize a new identity credential. + * + * <p>Credentials cannot be updated or modified after creation; any changes require deletion and + * re-creation. + * + * Use {@link IdentityCredentialStore#createCredential(String, String)} to create a new credential. + */ +public abstract class WritableIdentityCredential { + /** + * Generates and returns an X.509 certificate chain for the CredentialKey which identifies this + * credential to the issuing authority. The certificate contains an + * <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a> + * attestation extension which describes the key and the security hardware in which it lives. + * + * <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates + * it is an Identity Credential key (which can only sign/MAC very specific messages) and not + * an Android Keystore key (which can be used to sign/MAC anything). + * + * <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not + * limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is + * present, the passed in challenge is present, the device has verified boot enabled, that each + * certificate in the chain is signed by its successor, that none of the certificates have been + * revoked and so on. + * + * <p>It is not strictly necessary to use this method to provision a credential if the issuing + * authority doesn't care about the nature of the security hardware. If called, however, this + * method must be called before {@link #personalize(PersonalizationData)}. + * + * @param challenge is a byte array whose contents should be unique, fresh and provided by + * the issuing authority. The value provided is embedded in the attestation + * extension and enables the issuing authority to verify that the attestation + * certificate is fresh. + * @return the X.509 certificate for this credential's CredentialKey. + */ + public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain( + @NonNull byte[] challenge); + + /** + * Stores all of the data in the credential, with the specified access control profiles. + * + * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey with payload + * set to {@code ProofOfProvisioning} as defined below. + * + * <pre> + * ProofOfProvisioning = [ + * "ProofOfProvisioning", ; tstr + * tstr, ; DocType + * [ * AccessControlProfile ], + * ProvisionedData, + * bool ; true if this is a test credential, should + * ; always be false. + * ] + * + * AccessControlProfile = { + * "id": uint, + * ? "readerCertificate" : bstr, + * ? ( + * "userAuthenticationRequired" : bool, + * "timeoutMillis" : uint, + * ) + * } + * + * ProvisionedData = { + * * Namespace => [ + Entry ] + * }, + * + * Namespace = tstr + * + * Entry = { + * "name" : tstr, + * "value" : any, + * "accessControlProfiles" : [ * uint ], + * } + * </pre> + * + * <p>This data structure provides a guarantee to the issuer about the data which may be + * returned in the CBOR returned by + * {@link ResultData#getAuthenticatedData()} during a credential + * presentation. + * + * @param personalizationData The data to provision, including access control profiles + * and data elements and their values, grouped into namespaces. + * @return A COSE_Sign1 data structure, see above. + */ + public abstract @NonNull byte[] personalize( + @NonNull PersonalizationData personalizationData); +} diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index d6b516fd5cf4..5a50245a3765 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -18,6 +18,8 @@ #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> +// TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead. +#include <surfacetexture/surface_texture_platform.h> #include "AutoBackendTextureRelease.h" #include "Matrix.h" #include "Properties.h" @@ -34,6 +36,7 @@ namespace uirenderer { DeferredLayerUpdater::DeferredLayerUpdater(RenderState& renderState) : mRenderState(renderState) , mBlend(false) + , mSurfaceTexture(nullptr, [](ASurfaceTexture*) {}) , mTransform(nullptr) , mGLContextAttached(false) , mUpdateTexImage(false) diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index 289f65c22ad3..c44c0d537fa7 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -21,8 +21,7 @@ #include <SkMatrix.h> #include <android/hardware_buffer.h> #include <cutils/compiler.h> -// TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead. -#include <gui/surfacetexture/surface_texture_platform.h> +#include <android/surface_texture.h> #include <map> #include <memory> @@ -37,7 +36,7 @@ namespace uirenderer { class AutoBackendTextureRelease; class RenderState; -typedef std::unique_ptr<ASurfaceTexture> AutoTextureRelease; +typedef std::unique_ptr<ASurfaceTexture, decltype(&ASurfaceTexture_release)> AutoTextureRelease; // Container to hold the properties a layer should be set to at the start // of a render pass diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 35a885f46919..b940cff04713 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -458,9 +458,15 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, const SkMatrix& preTransform) { SkAutoCanvasRestore saver(canvas, true); auto clipRestriction = preTransform.mapRect(clip).roundOut(); - canvas->androidFramework_setDeviceClipRestriction(clipRestriction); - canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction", - nullptr); + if (CC_UNLIKELY(mCaptureMode == CaptureMode::SingleFrameSKP + || mCaptureMode == CaptureMode::MultiFrameSKP)) { + canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction", + nullptr); + } else { + // clip drawing to dirty region only when not recording SKP files (which should contain all + // draw ops on every frame) + canvas->androidFramework_setDeviceClipRestriction(clipRestriction); + } canvas->concat(preTransform); // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293 diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 79b3886338b1..1a0f13943694 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -100,6 +100,8 @@ import java.util.stream.Collectors; * <tr><td>{@link #KEY_AAC_DRC_HEAVY_COMPRESSION}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies whether to use heavy compression.</td></tr> * <tr><td>{@link #KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the maximum number of channels the decoder outputs.</td></tr> * <tr><td>{@link #KEY_AAC_DRC_EFFECT_TYPE}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the MPEG-D DRC effect type to use.</td></tr> + * <tr><td>{@link #KEY_AAC_DRC_OUTPUT_LOUDNESS}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, returns the DRC output loudness.</td></tr> + * <tr><td>{@link #KEY_AAC_DRC_ALBUM_MODE}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the whether MPEG-D DRC Album Mode is active or not.</td></tr> * <tr><td>{@link #KEY_CHANNEL_MASK}</td><td>Integer</td><td>optional, a mask of audio channel assignments</td></tr> * <tr><td>{@link #KEY_ENCODER_DELAY}</td><td>Integer</td><td>optional, the number of frames to trim from the start of the decoded audio stream.</td></tr> * <tr><td>{@link #KEY_ENCODER_PADDING}</td><td>Integer</td><td>optional, the number of frames to trim from the end of the decoded audio stream.</td></tr> @@ -736,6 +738,37 @@ public final class MediaFormat { public static final String KEY_AAC_DRC_HEAVY_COMPRESSION = "aac-drc-heavy-compression"; /** + * A key to retrieve the output loudness of a decoded bitstream. + * <p>If loudness normalization is active, the value corresponds to the Target Reference Level + * (see {@link #KEY_AAC_DRC_TARGET_REFERENCE_LEVEL}).<br> + * If loudness normalization is not active, the value corresponds to the loudness metadata + * given in the bitstream. + * <p>The value is retrieved with getInteger() and is given as an integer value between 0 and + * 231. It is calculated as -4 * Output Loudness in LKFS. Therefore, it represents the range of + * 0 to -57.75 LKFS. + * <p>A value of -1 indicates that no loudness metadata is present in the bitstream. + * <p>Loudness metadata can originate from MPEG-4 DRC or MPEG-D DRC. + * <p>This key is only used during decoding. + */ + public static final String KEY_AAC_DRC_OUTPUT_LOUDNESS = "aac-drc-output-loudness"; + + /** + * A key describing the album mode for MPEG-D DRC as defined in ISO/IEC 23003-4. + * <p>The associated value is an integer and can be set to following values: + * <table> + * <tr><th>Value</th><th>Album Mode</th></tr> + * <tr><th>0</th><th>disabled</th></tr> + * <tr><th>1</th><th>enabled</th></tr> + * </table> + * <p>Disabled album mode leads to application of gain sequences for fading in and out, if + * provided in the bitstream. Enabled album mode makes use of dedicated album loudness + * information, if provided in the bitstream. + * <p>The default value is 0 (album mode disabled). + * <p>This key is only used during decoding. + */ + public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode"; + + /** * A key describing the FLAC compression level to be used (FLAC audio format only). * The associated value is an integer ranging from 0 (fastest, least compression) * to 8 (slowest, most compression). diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 8723fbb6f9fc..62ab2c94afeb 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -311,10 +311,10 @@ public final class Tuner implements AutoCloseable { * @throws SecurityException if the caller does not have appropriate permissions. * @throws IllegalStateException if {@code scan} is called again before {@link #stopScan()} is * called. - * @hide */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) - public int scan(@NonNull FrontendSettings settings, @ScanCallback.ScanType int scanType, + @Result + public int scan(@NonNull FrontendSettings settings, @TunerConstants.ScanType int scanType, @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) { TunerUtils.checkTunerPermission(mContext); if (mScanCallback != null || mScanCallbackExecutor != null) { @@ -337,7 +337,6 @@ public final class Tuner implements AutoCloseable { * If the method completes successfully, the frontend stopped previous scanning. * * @throws SecurityException if the caller does not have appropriate permissions. - * @hide */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index 5747c0b265f4..c03061948fe4 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -47,6 +47,27 @@ public final class TunerConstants { */ public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID; + /** @hide */ + @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScanType {} + /** + * Scan type undefined. + */ + public static final int SCAN_TYPE_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED; + /** + * Scan type auto. + * + * <p> Tuner will send {@link #onLocked} + */ + public static final int SCAN_TYPE_AUTO = Constants.FrontendScanType.SCAN_AUTO; + /** + * Blind scan. + * + * <p>Frequency range is not specified. The {@link android.media.tv.tuner.Tuner} will scan an + * implementation specific range. + */ + public static final int SCAN_TYPE_BLIND = Constants.FrontendScanType.SCAN_BLIND; /** @hide */ @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpInfo.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpInfo.java new file mode 100644 index 000000000000..9900fec3b28c --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpInfo.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.tuner.frontend; + +import android.annotation.SystemApi; + +/** PLP information for ATSC3. + * @hide + */ +@SystemApi +public class Atsc3PlpInfo { + private final int mPlpId; + private final boolean mLlsFlag; + + private Atsc3PlpInfo(int plpId, boolean llsFlag) { + mPlpId = plpId; + mLlsFlag = llsFlag; + } + + /** Gets PLP IDs. */ + public int getPlpId() { + return mPlpId; + } + + /** Gets LLS flag. */ + public boolean getLlsFlag() { + return mLlsFlag; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java index a825d6d486ae..f90144bc97ef 100644 --- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -16,41 +16,18 @@ package android.media.tv.tuner.frontend; -import android.annotation.IntDef; -import android.hardware.tv.tuner.V1_0.Constants; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; /** * Scan callback. * * @hide */ +@SystemApi public interface ScanCallback { - /** @hide */ - @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND}) - @Retention(RetentionPolicy.SOURCE) - @interface ScanType {} - /** - * Scan type undefined. - */ - int SCAN_TYPE_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED; - /** - * Scan type auto. - * - * <p> Tuner will send {@link #onLocked} - */ - int SCAN_TYPE_AUTO = Constants.FrontendScanType.SCAN_AUTO; - /** - * Blind scan. - * - * <p>Frequency range is not specified. The {@link android.media.tv.tuner.Tuner} will scan an - * implementation specific range. - */ - int SCAN_TYPE_BLIND = Constants.FrontendScanType.SCAN_BLIND; - /** Scan locked the signal. */ void onLocked(); @@ -58,22 +35,22 @@ public interface ScanCallback { void onScanStopped(); /** scan progress percent (0..100) */ - void onProgress(int percent); + void onProgress(@IntRange(from = 0, to = 100) int percent); /** Signal frequencies in Hertz */ - void onFrequenciesReport(int[] frequency); + void onFrequenciesReport(@NonNull int[] frequency); /** Symbols per second */ - void onSymbolRates(int[] rate); + void onSymbolRates(@NonNull int[] rate); /** Locked Plp Ids for DVBT2 frontend. */ - void onPlpIds(int[] plpIds); + void onPlpIds(@NonNull int[] plpIds); /** Locked group Ids for DVBT2 frontend. */ - void onGroupIds(int[] groupIds); + void onGroupIds(@NonNull int[] groupIds); /** Stream Ids. */ - void onInputStreamIds(int[] inputStreamIds); + void onInputStreamIds(@NonNull int[] inputStreamIds); /** Locked signal standard for DVBS. */ void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard); @@ -85,7 +62,7 @@ public interface ScanCallback { void onAnalogSifStandard(@AnalogFrontendSettings.SifStandard int sif); /** PLP status in a tuned frequency band for ATSC3 frontend. */ - void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos); + void onAtsc3PlpInfos(@NonNull Atsc3PlpInfo[] atsc3PlpInfos); /** Frontend hierarchy. */ void onHierarchy(@DvbtFrontendSettings.Hierarchy int hierarchy); @@ -93,24 +70,4 @@ public interface ScanCallback { /** Frontend hierarchy. */ void onSignalType(@AnalogFrontendSettings.SignalType int signalType); - /** PLP information for ATSC3. */ - class Atsc3PlpInfo { - private final int mPlpId; - private final boolean mLlsFlag; - - private Atsc3PlpInfo(int plpId, boolean llsFlag) { - mPlpId = plpId; - mLlsFlag = llsFlag; - } - - /** Gets PLP IDs. */ - public int getPlpId() { - return mPlpId; - } - - /** Gets LLS flag. */ - public boolean getLlsFlag() { - return mLlsFlag; - } - } } diff --git a/native/android/Android.bp b/native/android/Android.bp index 9d93c9b7b605..0c6f507787d9 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -47,7 +47,6 @@ cc_library_shared { "sensor.cpp", "sharedmem.cpp", "storage_manager.cpp", - "surface_texture.cpp", "surface_control.cpp", "system_fonts.cpp", "trace.cpp", @@ -70,6 +69,8 @@ cc_library_shared { "libnetd_client", "libhwui", "libxml2", + "libEGL", + "libGLESv2", "android.hardware.configstore@1.0", "android.hardware.configstore-utils", ], diff --git a/native/android/surface_texture.cpp b/native/android/surface_texture.cpp deleted file mode 100644 index 3049ec16e2d4..000000000000 --- a/native/android/surface_texture.cpp +++ /dev/null @@ -1,70 +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. - */ - -#include <android/surface_texture.h> -#include <android/surface_texture_jni.h> - -#define LOG_TAG "ASurfaceTexture" - -#include <utils/Log.h> - -#include <gui/Surface.h> - -#include <gui/surfacetexture/surface_texture_platform.h> - -#include <android_runtime/android_graphics_SurfaceTexture.h> - -using namespace android; - -ASurfaceTexture* ASurfaceTexture_fromSurfaceTexture(JNIEnv* env, jobject surfacetexture) { - if (!surfacetexture || !android_SurfaceTexture_isInstanceOf(env, surfacetexture)) { - return nullptr; - } - auto consumer = SurfaceTexture_getSurfaceTexture(env, surfacetexture); - auto producer = SurfaceTexture_getProducer(env, surfacetexture); - return ASurfaceTexture_create(consumer, producer); -} - -ANativeWindow* ASurfaceTexture_acquireANativeWindow(ASurfaceTexture* st) { - sp<Surface> surface = new Surface(st->producer); - ANativeWindow* win(surface.get()); - ANativeWindow_acquire(win); - return win; -} - -void ASurfaceTexture_release(ASurfaceTexture* st) { - delete st; -} - -int ASurfaceTexture_attachToGLContext(ASurfaceTexture* st, uint32_t tex) { - return st->consumer->attachToContext(tex); -} - -int ASurfaceTexture_detachFromGLContext(ASurfaceTexture* st) { - return st->consumer->detachFromContext(); -} - -int ASurfaceTexture_updateTexImage(ASurfaceTexture* st) { - return st->consumer->updateTexImage(); -} - -void ASurfaceTexture_getTransformMatrix(ASurfaceTexture* st, float mtx[16]) { - st->consumer->getTransformMatrix(mtx); -} - -int64_t ASurfaceTexture_getTimestamp(ASurfaceTexture* st) { - return st->consumer->getTimestamp(); -} diff --git a/packages/SystemUI/res/drawable/action_chip_background.xml b/packages/SystemUI/res/drawable/action_chip_background.xml index fc3dfeb9c1fd..ac227a604e56 100644 --- a/packages/SystemUI/res/drawable/action_chip_background.xml +++ b/packages/SystemUI/res/drawable/action_chip_background.xml @@ -19,7 +19,7 @@ android:color="@color/global_screenshot_button_ripple"> <item android:id="@android:id/background"> <shape android:shape="rectangle"> - <stroke android:width="1dp" android:color="@color/global_screenshot_button_text"/> + <stroke android:width="1dp" android:color="@color/global_screenshot_button_border"/> <solid android:color="@color/global_screenshot_button_background"/> <corners android:radius="@dimen/screenshot_button_corner_radius"/> </shape> diff --git a/packages/SystemUI/res/drawable/ic_arrow_downward.xml b/packages/SystemUI/res/drawable/ic_arrow_downward.xml new file mode 100644 index 000000000000..ddd075dd17cc --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_arrow_downward.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z" + android:fillColor="@android:color/white"/> +</vector> diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml new file mode 100644 index 000000000000..163015b7b0f0 --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <gradient + android:angle="90" + android:startColor="#1f000000" + android:endColor="#00000000"/> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshot_rounded_corners.xml b/packages/SystemUI/res/drawable/screenshot_rounded_corners.xml new file mode 100644 index 000000000000..fb8e9b7373f5 --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_rounded_corners.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="@dimen/screenshot_button_corner_radius"/> +</shape> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 995cb7d48e60..1f7def2bc956 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -1,51 +1,75 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> +<!-- + ~ Copyright (C) 2011 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. + --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <ImageView android:id="@+id/global_screenshot_background" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:id="@+id/global_screenshot_background" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@android:color/black" - android:visibility="gone" /> - <ImageView android:id="@+id/global_screenshot" + android:visibility="gone"/> + <ImageView + android:id="@+id/global_screenshot_actions_background" + android:layout_height="400dp" + android:layout_width="match_parent" + android:layout_gravity="bottom|center" + android:src="@drawable/screenshot_actions_background_protection" + android:alpha="0"/> + <HorizontalScrollView + android:id="@+id/global_screenshot_actions_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom|center" + android:elevation="3dp" + android:fillViewport="true" + android:layout_marginHorizontal="@dimen/screenshot_action_container_margin_horizontal" + android:gravity="center" + android:paddingLeft="@dimen/screenshot_action_container_padding_left" + android:paddingRight="@dimen/screenshot_action_container_padding_right" + android:paddingVertical="@dimen/screenshot_action_container_padding_vertical" + android:visibility="gone" + android:scrollbars="none" + android:background="@drawable/action_chip_container_background"> + <LinearLayout + android:id="@+id/global_screenshot_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </HorizontalScrollView> + <ImageView + android:id="@+id/global_screenshot" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:background="@drawable/screenshot_panel" + android:elevation="8dp" android:visibility="gone" - android:adjustViewBounds="true" /> - <ImageView android:id="@+id/global_screenshot_flash" + android:background="@drawable/screenshot_rounded_corners" + android:adjustViewBounds="true"/> + <ImageView + android:id="@+id/global_screenshot_flash" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@android:color/white" - android:visibility="gone" /> + android:visibility="gone"/> <com.android.systemui.screenshot.ScreenshotSelectorView android:id="@+id/global_screenshot_selector" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" android:pointerIcon="crosshair"/> - <LinearLayout - android:id="@+id/global_screenshot_actions" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="bottom|center" - android:gravity="center" - android:paddingVertical="@dimen/screenshot_action_container_padding" - android:visibility="gone" - android:background="@drawable/action_chip_container_background"/> + </FrameLayout> diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml index 366abaa1366d..79867a16b243 100644 --- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml +++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml @@ -36,5 +36,6 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end" android:textSize="@dimen/screenshot_action_chip_text_size" + android:textStyle="bold" android:textColor="@color/global_screenshot_button_text"/> </com.android.systemui.screenshot.ScreenshotActionChip> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index c1424657cf4f..8dd2a8b9d07a 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -182,8 +182,10 @@ <!-- Global screenshot actions --> <color name="global_screenshot_button_background">#F5F5F5</color> + <color name="global_screenshot_button_text">#000000</color> + <color name="global_screenshot_button_border">@color/GM2_grey_300</color> <color name="global_screenshot_button_ripple">#1f000000</color> - <color name="global_screenshot_button_text">@color/GM2_blue_500</color> + <color name="global_screenshot_button_icon">@color/GM2_blue_500</color> <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 53df02588f11..26af4ec31e47 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -295,17 +295,24 @@ <!-- The padding on the global screenshot background image --> <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> + <dimen name="global_screenshot_x_scale">80dp</dimen> + <dimen name="screenshot_offset_y">48dp</dimen> + <dimen name="screenshot_offset_x">16dp</dimen> + <dimen name="screenshot_action_container_offset_y">32dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> - <dimen name="screenshot_action_container_padding">10dp</dimen> + <dimen name="screenshot_action_container_padding_vertical">10dp</dimen> + <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen> + <dimen name="screenshot_action_container_padding_left">100dp</dimen> + <dimen name="screenshot_action_container_padding_right">8dp</dimen> <!-- Radius of the chip background on global screenshot actions --> <dimen name="screenshot_button_corner_radius">20dp</dimen> <dimen name="screenshot_action_chip_margin_horizontal">4dp</dimen> <dimen name="screenshot_action_chip_padding_vertical">10dp</dimen> <dimen name="screenshot_action_chip_icon_size">20dp</dimen> - <dimen name="screenshot_action_chip_padding_start">4dp</dimen> + <dimen name="screenshot_action_chip_padding_start">8dp</dimen> <!-- Padding between icon and text --> <dimen name="screenshot_action_chip_padding_middle">8dp</dimen> - <dimen name="screenshot_action_chip_padding_end">12dp</dimen> + <dimen name="screenshot_action_chip_padding_end">16dp</dimen> <dimen name="screenshot_action_chip_text_size">14sp</dimen> @@ -1079,6 +1086,10 @@ <dimen name="logout_button_margin_bottom">12dp</dimen> <dimen name="logout_button_corner_radius">2dp</dimen> + <!-- Blur radius on status bar window and power menu --> + <dimen name="min_window_blur_radius">1px</dimen> + <dimen name="max_window_blur_radius">100px</dimen> + <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> <!-- How much each bubble is elevated. --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 91299386d68a..639005ba1c40 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -920,6 +920,10 @@ <string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string> <!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] --> <string name="quick_settings_dark_mode_secondary_label_until_sunrise">Until sunrise</string> + <!-- QuickSettings: Secondary text for when the Dark theme will be enabled at some user-selected time. [CHAR LIMIT=20] --> + <string name="quick_settings_dark_mode_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string> + <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] --> + <string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string> <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] --> <string name="quick_settings_nfc_label">NFC</string> diff --git a/packages/SystemUI/scripts/update_shared_lib.sh b/packages/SystemUI/scripts/update_shared_lib.sh index 05374934be2a..25f723fb6cd0 100755 --- a/packages/SystemUI/scripts/update_shared_lib.sh +++ b/packages/SystemUI/scripts/update_shared_lib.sh @@ -1,6 +1,7 @@ #!/bin/sh NUM_ARGS=$# +JAR_DESTINATION="$1/prebuilts/framework_intermediates/quickstep/libs/sysui_shared.jar" has_croot() { declare -F croot > /dev/null @@ -25,7 +26,6 @@ main() { pushd . croot mma -j16 SystemUISharedLib - JAR_DESTINATION="$1/prebuilts/framework_intermediates/quickstep/libs/sysui_shared.jar" cp out/target/product/$TARGET_PRODUCT/obj/JAVA_LIBRARIES/SystemUISharedLib_intermediates/javalib.jar $JAR_DESTINATION popd fi diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 447181813888..fe6e44b5de0a 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -12,8 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -android_library { +genrule { + name: "statslog-SystemUI-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module sysui --javaPackage com.android.systemui.shared.system --javaClass SysUiStatsLog", + out: ["com/android/systemui/shared/system/SysUiStatsLog.java"], +} + +java_library { + name: "SystemUI-statsd", + + srcs: [ + ":statslog-SystemUI-java-gen", + ], +} +android_library { name: "SystemUISharedLib", srcs: [ "src/**/*.java", @@ -21,7 +35,8 @@ android_library { ], static_libs: [ - "PluginCoreLib" + "PluginCoreLib", + "SystemUI-statsd", ], // Enforce that the library is built against java 7 so that there are diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java deleted file mode 100644 index 59ed1117182c..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java +++ /dev/null @@ -1,51 +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.shared.system; - -import android.util.StatsLog; - -/** - * Wrapper class to make StatsLog hidden API accessible. - */ -public class StatsLogCompat { - - /** - * StatsLog.write(StatsLog.LAUNCHER_EVENT, int action, int src_state, int dst_state, - * byte[] extension, boolean is_swipe_up_enabled); - */ - public static void write(int action, int srcState, int dstState, byte [] extension, - boolean swipeUpEnabled) { - StatsLog.write(19, action, srcState, dstState, extension, - swipeUpEnabled); - } - - /** - * StatsLog.write(StatsLog.STYLE_EVENT, action, colorPackageHash, - * fontPackageHash, shapePackageHash, clockPackageHash, - * launcherGrid, wallpaperCategoryHash, wallpaperIdHash, - * colorPreference, locationPreference); - */ - public static void write(int action, int colorPackageHash, - int fontPackageHash, int shapePackageHash, int clockPackageHash, - int launcherGrid, int wallpaperCategoryHash, int wallpaperIdHash, - int colorPreference, int locationPreference) { - StatsLog.write(179, action, colorPackageHash, - fontPackageHash, shapePackageHash, clockPackageHash, - launcherGrid, wallpaperCategoryHash, wallpaperIdHash, - colorPreference, locationPreference); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 91b22d178b9f..9ae446e58e13 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -28,7 +28,6 @@ import android.os.UserHandle; import android.util.AttributeSet; import android.util.Log; import android.util.Slog; -import android.util.StatsLog; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -50,6 +49,7 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; +import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.InjectionInflationController; @@ -615,8 +615,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { if (success) { - StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); // Force a garbage collection in an attempt to erase any lockscreen password left in // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard @@ -628,8 +628,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe Runtime.getRuntime().gc(); }); } else { - StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs); } mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index 35a65aaec70a..76adf04b21e7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import android.annotation.NonNull; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; @@ -26,6 +27,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -35,7 +37,6 @@ import android.view.View; import android.view.WindowManager; import android.widget.ImageView; -import com.android.internal.telephony.PhoneConstants; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -139,11 +140,11 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { // Sending empty PIN here to query the number of remaining PIN attempts new CheckSimPin("", mSubId) { - void onSimCheckResponse(final int result, final int attemptsRemaining) { - Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result + - " attemptsRemaining=" + attemptsRemaining); - if (attemptsRemaining >= 0) { - mRemainingAttempts = attemptsRemaining; + void onSimCheckResponse(final PinResult result) { + Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); setLockedSimMessage(); } } @@ -251,7 +252,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { mSubId = subId; } - abstract void onSimCheckResponse(final int result, final int attemptsRemaining); + abstract void onSimCheckResponse(@NonNull PinResult result); @Override public void run() { @@ -261,23 +262,23 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) .createForSubscriptionId(mSubId); - final int[] result = telephonyManager.supplyPinReportResult(mPin); - if (result == null || result.length == 0) { + final PinResult result = telephonyManager.supplyPinReportPinResult(mPin); + if (result == null) { Log.e(TAG, "Error result for supplyPinReportResult."); post(new Runnable() { @Override public void run() { - onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); + onSimCheckResponse(PinResult.getDefaultFailedResult()); } }); } else { if (DEBUG) { - Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]); + Log.v(TAG, "supplyPinReportResult returned: " + result.toString()); } post(new Runnable() { @Override public void run() { - onSimCheckResponse(result[0], result[1]); + onSimCheckResponse(result); } }); } @@ -330,17 +331,18 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { if (mCheckSimPinThread == null) { mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { @Override - void onSimCheckResponse(final int result, final int attemptsRemaining) { + void onSimCheckResponse(final PinResult result) { post(new Runnable() { @Override public void run() { - mRemainingAttempts = attemptsRemaining; + mRemainingAttempts = result.getAttemptsRemaining(); if (mSimUnlockProgressDialog != null) { mSimUnlockProgressDialog.hide(); } resetPasswordText(true /* animate */, - result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */); - if (result == PhoneConstants.PIN_RESULT_SUCCESS) { + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { Dependency.get(KeyguardUpdateMonitor.class) .reportSimUnlocked(mSubId); mRemainingAttempts = -1; @@ -350,14 +352,16 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } } else { mShowDefaultMessage = false; - if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) { - if (attemptsRemaining <= 2) { + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { + if (result.getAttemptsRemaining() <= 2) { // this is getting critical - show dialog - getSimRemainingAttemptsDialog(attemptsRemaining).show(); + getSimRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); } else { // show message mSecurityMessageDisplay.setMessage( - getPinPasswordErrorMessage(attemptsRemaining, false)); + getPinPasswordErrorMessage( + result.getAttemptsRemaining(), false)); } } else { // "PIN operation failed!" - no idea what this was and no way to @@ -367,7 +371,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " + " CheckSimPin.onSimCheckResponse: " + result - + " attemptsRemaining=" + attemptsRemaining); + + " attemptsRemaining=" + result.getAttemptsRemaining()); } mCallback.userActivity(); mCheckSimPinThread = null; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index dc681157f534..10dcbd6f9182 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import android.annotation.NonNull; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -25,6 +26,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -34,7 +36,6 @@ import android.view.View; import android.view.WindowManager; import android.widget.ImageView; -import com.android.internal.telephony.PhoneConstants; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -191,13 +192,16 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { // Sending empty PUK here to query the number of remaining PIN attempts new CheckSimPuk("", "", mSubId) { - void onSimLockChangedResponse(final int result, final int attemptsRemaining) { - Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result + - " attemptsRemaining=" + attemptsRemaining); - if (attemptsRemaining >= 0) { - mRemainingAttempts = attemptsRemaining; - mSecurityMessageDisplay.setMessage( - getPukPasswordErrorMessage(attemptsRemaining, true)); + void onSimLockChangedResponse(final PinResult result) { + if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL"); + else { + Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); + mSecurityMessageDisplay.setMessage( + getPukPasswordErrorMessage(result.getAttemptsRemaining(), true)); + } } } }.start(); @@ -311,7 +315,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { mSubId = subId; } - abstract void onSimLockChangedResponse(final int result, final int attemptsRemaining); + abstract void onSimLockChangedResponse(@NonNull PinResult result); @Override public void run() { @@ -319,23 +323,23 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) .createForSubscriptionId(mSubId); - final int[] result = telephonyManager.supplyPukReportResult(mPuk, mPin); - if (result == null || result.length == 0) { + final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); + if (result == null) { Log.e(TAG, "Error result for supplyPukReportResult."); post(new Runnable() { @Override public void run() { - onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); + onSimLockChangedResponse(PinResult.getDefaultFailedResult()); } }); } else { if (DEBUG) { - Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]); + Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); } post(new Runnable() { @Override public void run() { - onSimLockChangedResponse(result[0], result[1]); + onSimLockChangedResponse(result); } }); } @@ -402,7 +406,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { if (mCheckSimPukThread == null) { mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { @Override - void onSimLockChangedResponse(final int result, final int attemptsRemaining) { + void onSimLockChangedResponse(final PinResult result) { post(new Runnable() { @Override public void run() { @@ -410,29 +414,32 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { mSimUnlockProgressDialog.hide(); } resetPasswordText(true /* animate */, - result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */); - if (result == PhoneConstants.PIN_RESULT_SUCCESS) { + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { Dependency.get(KeyguardUpdateMonitor.class) .reportSimUnlocked(mSubId); mRemainingAttempts = -1; mShowDefaultMessage = true; if (mCallback != null) { - mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + mCallback.dismiss(true, + KeyguardUpdateMonitor.getCurrentUser()); } } else { mShowDefaultMessage = false; - if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) { + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { // show message mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( - attemptsRemaining, false)); - if (attemptsRemaining <= 2) { + result.getAttemptsRemaining(), false)); + if (result.getAttemptsRemaining() <= 2) { // this is getting critical - show dialog - getPukRemainingAttemptsDialog(attemptsRemaining).show(); + getPukRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); } else { // show message mSecurityMessageDisplay.setMessage( getPukPasswordErrorMessage( - attemptsRemaining, false)); + result.getAttemptsRemaining(), false)); } } else { mSecurityMessageDisplay.setMessage(getContext().getString( @@ -440,7 +447,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { } if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " + " UpdateSim.onSimCheckResponse: " - + " attemptsRemaining=" + attemptsRemaining); + + " attemptsRemaining=" + result.getAttemptsRemaining()); mStateMachine.reset(); } mCheckSimPukThread = null; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index c1705dbb4e4b..53a7cfdfd268 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -42,7 +42,6 @@ import android.os.RemoteException; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; -import android.util.StatsLog; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; @@ -52,6 +51,7 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; +import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.AlphaOptimizedButton; /** @@ -467,7 +467,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mStackView.collapseStack(() -> { mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser()); logBubbleClickEvent(mBubble, - StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); + SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); }); } } @@ -564,7 +564,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList */ private void logBubbleClickEvent(Bubble bubble, int action) { StatusBarNotification notification = bubble.getEntry().getSbn(); - StatsLog.write(StatsLog.BUBBLE_UI_CHANGED, + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, notification.getPackageName(), notification.getNotification().getChannelId(), notification.getId(), diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 15c1c5524a74..02314bfe7251 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -45,7 +45,6 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.service.notification.StatusBarNotification; import android.util.Log; -import android.util.StatsLog; import android.view.Choreographer; import android.view.DisplayCutout; import android.view.Gravity; @@ -73,6 +72,7 @@ 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.shared.system.SysUiStatsLog; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -735,7 +735,7 @@ public class BubbleStackView extends FrameLayout { ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters); animateInFlyoutForBubble(bubble); requestUpdate(); - logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); + logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED); updatePointerPosition(); } @@ -749,7 +749,7 @@ public class BubbleStackView extends FrameLayout { if (removedIndex >= 0) { mBubbleContainer.removeViewAt(removedIndex); bubble.cleanupExpandedState(); - logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); + logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); } else { Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); } @@ -760,7 +760,7 @@ public class BubbleStackView extends FrameLayout { void updateBubble(Bubble bubble) { animateInFlyoutForBubble(bubble); requestUpdate(); - logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED); + logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED); } public void updateBubbleOrder(List<Bubble> bubbles) { @@ -800,8 +800,9 @@ public class BubbleStackView extends FrameLayout { updateExpandedBubble(); updatePointerPosition(); requestUpdate(); - logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); - logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); + logBubbleEvent(previouslySelected, + SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); + logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); notifyExpansionChanged(previouslySelected, false /* expanded */); notifyExpansionChanged(bubbleToSelect, true /* expanded */); }); @@ -823,12 +824,12 @@ public class BubbleStackView extends FrameLayout { } if (mIsExpanded) { animateCollapse(); - logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); + logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); } else { animateExpansion(); // TODO: move next line to BubbleData - logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); - logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); + logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); + logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); } notifyExpansionChanged(mExpandedBubble, mIsExpanded); } @@ -845,7 +846,7 @@ public class BubbleStackView extends FrameLayout { } mBubbleData.dismissAll(reason); logBubbleEvent(null /* no bubble associated with bubble stack dismiss */, - StatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED); + SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED); } /** @@ -1093,7 +1094,7 @@ public class BubbleStackView extends FrameLayout { final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY); logBubbleEvent(null /* no bubble associated with bubble stack move */, - StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED); + SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED); mStackOnLeftOrWillBe = newStackX <= 0; updateBubbleZOrdersAndDotPosition(true /* animate */); @@ -1443,7 +1444,7 @@ public class BubbleStackView extends FrameLayout { }); mFlyout.removeCallbacks(mHideFlyout); mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER); - logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT); + logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT); } /** Hide the flyout immediately and cancel any pending hide runnables. */ @@ -1641,7 +1642,7 @@ public class BubbleStackView extends FrameLayout { private void logBubbleEvent(@Nullable Bubble bubble, int action) { if (bubble == null || bubble.getEntry() == null || bubble.getEntry().getSbn() == null) { - StatsLog.write(StatsLog.BUBBLE_UI_CHANGED, + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, null /* package name */, null /* notification channel */, 0 /* notification ID */, @@ -1655,7 +1656,7 @@ public class BubbleStackView extends FrameLayout { false /* isAppForeground (unused) */); } else { StatusBarNotification notification = bubble.getEntry().getSbn(); - StatsLog.write(StatsLog.BUBBLE_UI_CHANGED, + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, notification.getPackageName(), notification.getNotification().getChannelId(), notification.getId(), diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index a6fa4146300d..7506be09e589 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -19,17 +19,22 @@ package com.android.systemui.dagger; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Resources; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.BootCompleteCache; import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.DumpController; import com.android.systemui.assist.AssistModule; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; import com.android.systemui.stackdivider.Divider; +import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.StatusBarWindowBlurController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.people.PeopleHubModule; @@ -82,6 +87,16 @@ public abstract class SystemUIModule { keyguardUpdateMonitor, dumpController); } + @Singleton + @Provides + @Nullable + static StatusBarWindowBlurController providesBlurController(BlurUtils blurUtils, + @Main Resources resources, SysuiStatusBarStateController statusBarStateController, + DumpController dumpController) { + return blurUtils.supportsBlursOnWindows() ? new StatusBarWindowBlurController(resources, + statusBarStateController, blurUtils, dumpController) : null; + } + /** */ @Binds public abstract NotificationRowBinder bindNotificationRowBinder( diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 36a4a10b8a3f..37351985b3bd 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -98,6 +98,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -157,6 +158,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final IActivityManager mIActivityManager; private final TelecomManager mTelecomManager; private final MetricsLogger mMetricsLogger; + private final BlurUtils mBlurUtils; private ArrayList<Action> mItems; private ActionsDialog mDialog; @@ -177,6 +179,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final ScreenshotHelper mScreenshotHelper; private final ScreenRecordHelper mScreenRecordHelper; private final ActivityStarter mActivityStarter; + private final SysuiColorExtractor mSysuiColorExtractor; + private final IStatusBarService mStatusBarService; + private final NotificationShadeWindowController mNotificationShadeWindowController; private GlobalActionsPanelPlugin mPanelPlugin; /** @@ -192,7 +197,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, ConfigurationController configurationController, ActivityStarter activityStarter, KeyguardStateController keyguardStateController, UserManager userManager, TrustManager trustManager, IActivityManager iActivityManager, - TelecomManager telecomManager, MetricsLogger metricsLogger) { + TelecomManager telecomManager, MetricsLogger metricsLogger, + BlurUtils blurUtils, SysuiColorExtractor colorExtractor, + IStatusBarService statusBarService, + NotificationShadeWindowController notificationShadeWindowController) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -208,6 +216,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mIActivityManager = iActivityManager; mTelecomManager = telecomManager; mMetricsLogger = metricsLogger; + mBlurUtils = blurUtils; + mSysuiColorExtractor = colorExtractor; + mStatusBarService = statusBarService; + mNotificationShadeWindowController = notificationShadeWindowController; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -443,8 +455,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mKeyguardManager.isDeviceLocked()) : null; - ActionsDialog dialog = new ActionsDialog( - mContext, mAdapter, panelViewController, isControlsEnabled(mContext)); + ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController, + mBlurUtils, mSysuiColorExtractor, mStatusBarService, + mNotificationShadeWindowController, isControlsEnabled(mContext)); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setKeyguardShowing(mKeyguardShowing); @@ -1529,18 +1542,21 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private ResetOrientationData mResetOrientationData; private boolean mHadTopUi; private final NotificationShadeWindowController mNotificationShadeWindowController; - private boolean mControlsEnabled; + private final BlurUtils mBlurUtils; + private final boolean mControlsEnabled; ActionsDialog(Context context, MyAdapter adapter, - GlobalActionsPanelPlugin.PanelViewController plugin, + GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils, + SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, + NotificationShadeWindowController notificationShadeWindowController, boolean controlsEnabled) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; - mColorExtractor = Dependency.get(SysuiColorExtractor.class); - mStatusBarService = Dependency.get(IStatusBarService.class); - mNotificationShadeWindowController = - Dependency.get(NotificationShadeWindowController.class); + mBlurUtils = blurUtils; + mColorExtractor = sysuiColorExtractor; + mStatusBarService = statusBarService; + mNotificationShadeWindowController = notificationShadeWindowController; mControlsEnabled = controlsEnabled; // Window initialization @@ -1735,9 +1751,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, .setDuration(300) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setUpdateListener(animation -> { - int alpha = (int) ((Float) animation.getAnimatedValue() - * mScrimAlpha * 255); + float animatedValue = animation.getAnimatedFraction(); + int alpha = (int) (animatedValue * mScrimAlpha * 255); mBackgroundDrawable.setAlpha(alpha); + mBlurUtils.applyBlur(mGlobalActionsLayout.getViewRootImpl(), + mBlurUtils.radiusForRatio(animatedValue)); }) .start(); } @@ -1759,9 +1777,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, .withEndAction(this::completeDismiss) .setInterpolator(new LogAccelerateInterpolator()) .setUpdateListener(animation -> { - int alpha = (int) ((1f - (Float) animation.getAnimatedValue()) - * mScrimAlpha * 255); + float animatedValue = 1f - animation.getAnimatedFraction(); + int alpha = (int) (animatedValue * mScrimAlpha * 255); mBackgroundDrawable.setAlpha(alpha); + mBlurUtils.applyBlur(mGlobalActionsLayout.getViewRootImpl(), + mBlurUtils.radiusForRatio(animatedValue)); }) .start(); dismissPanel(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 6afa0bfcbdb6..eba2e7874574 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -19,17 +19,11 @@ package com.android.systemui.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN; -import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; -import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN; - import android.animation.AnimationHandler; import android.animation.Animator; -import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; -import android.animation.RectEvaluator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.animation.TimeAnimator; +import android.annotation.Nullable; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; import android.app.IActivityTaskManager; @@ -42,13 +36,16 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.util.Log; -import android.view.animation.Interpolator; + +import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.os.SomeArgs; import com.android.systemui.pip.PipSnapAlgorithm; import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.FlingAnimationUtils; +import com.android.systemui.util.animation.FloatProperties; +import com.android.systemui.util.animation.PhysicsAnimator; import java.io.PrintWriter; @@ -60,18 +57,14 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call private static final String TAG = "PipMotionHelper"; private static final boolean DEBUG = false; - private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); - - private static final int DEFAULT_MOVE_STACK_DURATION = 225; - private static final int SNAP_STACK_DURATION = 225; - private static final int DRAG_TO_TARGET_DISMISS_STACK_DURATION = 375; - private static final int DRAG_TO_DISMISS_STACK_DURATION = 175; private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; private static final int EXPAND_STACK_TO_MENU_DURATION = 250; private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300; - private static final int MINIMIZE_STACK_MAX_DURATION = 200; private static final int SHIFT_DURATION = 300; + /** Friction to use for PIP when it moves via physics fling animations. */ + private static final float DEFAULT_FRICTION = 2f; + // The fraction of the stack width that the user has to drag offscreen to minimize the PiP private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f; // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP @@ -89,12 +82,39 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call private PipMenuActivityController mMenuController; private PipSnapAlgorithm mSnapAlgorithm; private FlingAnimationUtils mFlingAnimationUtils; - private AnimationHandler mAnimationHandler; - private final Rect mBounds = new Rect(); private final Rect mStableInsets = new Rect(); - private ValueAnimator mBoundsAnimator = null; + /** PIP's current bounds on the screen. */ + private final Rect mBounds = new Rect(); + + /** + * Bounds that are animated using the physics animator. PIP is moved to these bounds whenever + * the {@link #mVsyncTimeAnimator} ticks. + */ + private final Rect mAnimatedBounds = new Rect(); + + /** + * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations. + */ + private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance( + mAnimatedBounds); + + /** + * Time animator whose frame timing comes from the SurfaceFlinger vsync frame provider. At each + * frame, PIP is moved to {@link #mAnimatedBounds}, which are animated asynchronously using + * physics animations. + */ + private TimeAnimator mVsyncTimeAnimator; + + /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ + private PhysicsAnimator.FlingConfig mFlingConfigX; + private PhysicsAnimator.FlingConfig mFlingConfigY; + + /** SpringConfig to use for fling-then-spring animations. */ + private final PhysicsAnimator.SpringConfig mSpringConfig = + new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); public PipMotionHelper(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, @@ -106,9 +126,39 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; mFlingAnimationUtils = flingAnimationUtils; - mAnimationHandler = new AnimationHandler(); - mAnimationHandler.setProvider(new SfVsyncFrameCallbackProvider()); + final AnimationHandler vsyncFrameCallbackProvider = new AnimationHandler(); + vsyncFrameCallbackProvider.setProvider(new SfVsyncFrameCallbackProvider()); + onConfigurationChanged(); + + // Construct a time animator that uses the vsync frame provider. Physics animations can't + // use custom frame providers, since they rely on constant time between frames to run the + // physics simulations. To work around this, we physically-animate a second set of bounds, + // and apply those animating bounds to the PIP in-sync via this TimeAnimator. + mVsyncTimeAnimator = new TimeAnimator() { + @Override + public AnimationHandler getAnimationHandler() { + return vsyncFrameCallbackProvider; + } + }; + + // When the time animator ticks, move PIP to the animated bounds. + mVsyncTimeAnimator.setTimeListener( + (animation, totalTime, deltaTime) -> + resizePipUnchecked(mAnimatedBounds)); + + // Add a listener for cancel/end events that moves PIP to the final animated bounds. + mVsyncTimeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + resizePipUnchecked(mAnimatedBounds); + } + + @Override + public void onAnimationEnd(Animator animation) { + resizePipUnchecked(mAnimatedBounds); + } + }); } /** @@ -241,88 +291,85 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call } /** - * Flings the minimized PiP to the closest minimized snap target. + * Animates the PiP to the minimized state, slightly offscreen. */ - Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) { - cancelAnimations(); - // We currently only allow flinging the minimized stack up and down, so just lock the - // movement bounds to the current stack bounds horizontally - movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left, - movementBounds.bottom); - Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds, - 0 /* velocityX */, velocityY, dragStartPosition); - if (!mBounds.equals(toBounds)) { - mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN); - mFlingAnimationUtils.apply(mBoundsAnimator, 0, - distanceBetweenRectOffsets(mBounds, toBounds), - velocityY); - mBoundsAnimator.start(); + void animateToClosestMinimizedState(Rect movementBounds, @Nullable Runnable updateAction) { + final Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds); + + prepareForBoundsAnimation(movementBounds); + + mAnimatedBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) + .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); + + if (updateAction != null) { + mAnimatedBoundsPhysicsAnimator.addUpdateListener( + (target, values) -> updateAction.run()); } - return toBounds; + + startBoundsAnimation(); } /** - * Animates the PiP to the minimized state, slightly offscreen. + * Flings the PiP to the closest snap target. */ - Rect animateToClosestMinimizedState(Rect movementBounds, - AnimatorUpdateListener updateListener) { - cancelAnimations(); - Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds); - if (!mBounds.equals(toBounds)) { - mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, - MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN); - if (updateListener != null) { - mBoundsAnimator.addUpdateListener(updateListener); - } - mBoundsAnimator.start(); - } - return toBounds; + void flingToSnapTarget( + float velocityX, float velocityY, Rect movementBounds, Runnable updateAction, + @Nullable Runnable endAction) { + prepareForBoundsAnimation(movementBounds); + + mAnimatedBoundsPhysicsAnimator + .flingThenSpring( + FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig, + true /* flingMustReachMinOrMax */) + .flingThenSpring( + FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig) + .addUpdateListener((target, values) -> updateAction.run()) + .withEndActions(endAction); + + startBoundsAnimation(); } /** - * Flings the PiP to the closest snap target. + * Animates the PiP to the closest snap target. */ - Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds, - AnimatorUpdateListener updateListener, AnimatorListener listener, - Point startPosition) { - cancelAnimations(); - Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds, - velocityX, velocityY, startPosition); - if (!mBounds.equals(toBounds)) { - mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN); - mFlingAnimationUtils.apply(mBoundsAnimator, 0, - distanceBetweenRectOffsets(mBounds, toBounds), - velocity); - if (updateListener != null) { - mBoundsAnimator.addUpdateListener(updateListener); - } - if (listener != null){ - mBoundsAnimator.addListener(listener); - } - mBoundsAnimator.start(); - } - return toBounds; + void animateToClosestSnapTarget(Rect movementBounds) { + prepareForBoundsAnimation(movementBounds); + + final Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds); + mAnimatedBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) + .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); + + startBoundsAnimation(); } /** - * Animates the PiP to the closest snap target. + * Animates the dismissal of the PiP off the edge of the screen. */ - Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener, - AnimatorListener listener) { - cancelAnimations(); - Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds); - if (!mBounds.equals(toBounds)) { - mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION, - FAST_OUT_SLOW_IN); - if (updateListener != null) { - mBoundsAnimator.addUpdateListener(updateListener); - } - if (listener != null){ - mBoundsAnimator.addListener(listener); - } - mBoundsAnimator.start(); + void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) { + final float velocity = PointF.length(velocityX, velocityY); + final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); + final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling); + + // Set the animated bounds to start at the current bounds. We don't need to rebuild the + // fling configs here via prepareForBoundsAnimation, since animateDismiss isn't provided + // with new movement bounds. + mAnimatedBounds.set(mBounds); + + // Animate to the dismiss end point, and then dismiss PIP. + mAnimatedBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig) + .spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig) + .withEndActions(this::dismissPip); + + // If we were provided with an update action, run it whenever there's an update. + if (updateAction != null) { + mAnimatedBoundsPhysicsAnimator.addUpdateListener( + (target, values) -> updateAction.run()); } - return toBounds; + + startBoundsAnimation(); } /** @@ -378,64 +425,42 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call } /** - * Animates the dismissal of the PiP off the edge of the screen. + * Cancels all existing animations. */ - Rect animateDismiss(Rect pipBounds, float velocityX, float velocityY, - AnimatorUpdateListener listener) { - cancelAnimations(); - final float velocity = PointF.length(velocityX, velocityY); - final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); - Point p = getDismissEndPoint(pipBounds, velocityX, velocityY, isFling); - Rect toBounds = new Rect(pipBounds); - toBounds.offsetTo(p.x, p.y); - mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DRAG_TO_DISMISS_STACK_DURATION, - FAST_OUT_LINEAR_IN); - mBoundsAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - dismissPip(); - } - }); - if (isFling) { - mFlingAnimationUtils.apply(mBoundsAnimator, 0, - distanceBetweenRectOffsets(mBounds, toBounds), velocity); - } - if (listener != null) { - mBoundsAnimator.addUpdateListener(listener); - } - mBoundsAnimator.start(); - return toBounds; + private void cancelAnimations() { + mAnimatedBoundsPhysicsAnimator.cancel(); + mVsyncTimeAnimator.cancel(); } /** - * Cancels all existing animations. + * Set new fling configs whose min/max values respect the given movement bounds, and set the + * animated bounds to PIP's current 'real' bounds. */ - void cancelAnimations() { - if (mBoundsAnimator != null) { - mBoundsAnimator.cancel(); - mBoundsAnimator = null; - } + private void prepareForBoundsAnimation(Rect movementBounds) { + mFlingConfigX = new PhysicsAnimator.FlingConfig( + DEFAULT_FRICTION, movementBounds.left, movementBounds.right); + mFlingConfigY = new PhysicsAnimator.FlingConfig( + DEFAULT_FRICTION, movementBounds.top, movementBounds.bottom); + + mAnimatedBounds.set(mBounds); } /** - * Creates an animation to move the PiP to give given {@param toBounds}. + * Starts the physics animator which will update the animated PIP bounds using physics + * animations, as well as the TimeAnimator which will apply those bounds to PIP at intervals + * synchronized with the SurfaceFlinger vsync frame provider. + * + * This will also add end actions to the bounds animator that cancel the TimeAnimator and update + * the 'real' bounds to equal the final animated bounds. */ - private ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration, - Interpolator interpolator) { - ValueAnimator anim = new ValueAnimator() { - @Override - public AnimationHandler getAnimationHandler() { - return mAnimationHandler; - } - }; - anim.setObjectValues(fromBounds, toBounds); - anim.setEvaluator(RECT_EVALUATOR); - anim.setDuration(duration); - anim.setInterpolator(interpolator); - anim.addUpdateListener((ValueAnimator animation) -> { - resizePipUnchecked((Rect) animation.getAnimatedValue()); - }); - return anim; + private void startBoundsAnimation() { + cancelAnimations(); + + mAnimatedBoundsPhysicsAnimator + .withEndActions( + mVsyncTimeAnimator::cancel) + .start(); + mVsyncTimeAnimator.start(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 95e3444f4779..09f163810d27 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -20,10 +20,6 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STAT import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.content.ComponentName; @@ -111,13 +107,6 @@ public class PipTouchHandler { } } }; - private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener = - new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateDismissFraction(); - } - }; // Behaviour states private int mMenuState = MENU_STATE_NONE; @@ -162,7 +151,7 @@ public class PipTouchHandler { @Override public void onPipMinimize() { setMinimizedStateInternal(true); - mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */); + mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateAction */); } @Override @@ -655,15 +644,6 @@ public class PipTouchHandler { float lastY = mStartPosition.y + mDelta.y; float left = lastX + lastDelta.x; float top = lastY + lastDelta.y; - if (!touchState.allowDraggingOffscreen() || !ENABLE_MINIMIZE) { - left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left)); - } - if (mEnableDimissDragToEdge) { - // Allow pip to move past bottom bounds - top = Math.max(mMovementBounds.top, top); - } else { - top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top)); - } // Add to the cumulative delta after bounding the position mDelta.x += left - lastX; @@ -720,8 +700,9 @@ public class PipTouchHandler { if (mMotionHelper.shouldDismissPip() || isFlingToBot) { MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, PipUtils.getTopPinnedActivity(mContext, mActivityManager)); - mMotionHelper.animateDismiss(mMotionHelper.getBounds(), vel.x, - vel.y, mUpdateScrimListener); + mMotionHelper.animateDismiss( + vel.x, vel.y, + PipTouchHandler.this::updateDismissFraction /* updateAction */); return true; } } @@ -739,8 +720,9 @@ public class PipTouchHandler { // minimize offset adjusted mMenuController.hideMenu(); } else { - mMotionHelper.animateToClosestMinimizedState(mMovementBounds, - mUpdateScrimListener); + mMotionHelper.animateToClosestMinimizedState( + mMovementBounds, + PipTouchHandler.this::updateDismissFraction /* updateAction */); } return true; } @@ -750,7 +732,7 @@ public class PipTouchHandler { setMinimizedStateInternal(false); } - AnimatorListenerAdapter postAnimationCallback = null; + Runnable endAction = null; if (mMenuState != MENU_STATE_NONE) { // If the menu is still visible, and we aren't minimized, then just poke the // menu so that it will timeout after the user stops touching it @@ -759,26 +741,20 @@ public class PipTouchHandler { } else { // If the menu is not visible, then we can still be showing the activity for the // dismiss overlay, so just finish it after the animation completes - postAnimationCallback = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mMenuController.hideMenu(); - } - }; + endAction = mMenuController::hideMenu; } if (isFling) { - mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds, - mUpdateScrimListener, postAnimationCallback, - mStartPosition); + mMotionHelper.flingToSnapTarget( + vel.x, vel.y, mMovementBounds, + PipTouchHandler.this::updateDismissFraction /* updateAction */, + endAction /* endAction */); } else { - mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener, - postAnimationCallback); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds); } } else if (mIsMinimized) { // This was a tap, so no longer minimized - mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */, - null /* animatorListener */); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds); setMinimizedStateInternal(false); } else if (mTouchState.isDoubleTap()) { // Expand to fullscreen if this is a double tap diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 7bc2a0d5c97d..8f1769b70d6c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -33,6 +33,8 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import javax.inject.Inject; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; /** * Quick Settings tile for: Night Mode / Dark Theme / Dark Mode. @@ -43,7 +45,7 @@ import javax.inject.Inject; public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements ConfigurationController.ConfigurationListener, BatteryController.BatteryStateChangeCallback { - + public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a"); private final Icon mIcon = ResourceIcon.get( com.android.internal.R.drawable.ic_qs_ui_mode_night); private final UiModeManager mUiModeManager; @@ -88,17 +90,28 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements protected void handleUpdateState(BooleanState state, Object arg) { int uiMode = mUiModeManager.getNightMode(); boolean powerSave = mBatteryController.isPowerSave(); - boolean isAuto = uiMode == UiModeManager.MODE_NIGHT_AUTO; boolean nightMode = (mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; if (powerSave) { state.secondaryLabel = mContext.getResources().getString( R.string.quick_settings_dark_mode_secondary_label_battery_saver); - } else if (isAuto) { + } else if (uiMode == UiModeManager.MODE_NIGHT_AUTO) { state.secondaryLabel = mContext.getResources().getString(nightMode ? R.string.quick_settings_dark_mode_secondary_label_until_sunrise : R.string.quick_settings_dark_mode_secondary_label_on_at_sunset); + } else if (uiMode == UiModeManager.MODE_NIGHT_CUSTOM) { + final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(mContext); + final LocalTime time; + if (nightMode) { + time = mUiModeManager.getCustomNightModeEnd(); + } else { + time = mUiModeManager.getCustomNightModeStart(); + } + state.secondaryLabel = mContext.getResources().getString(nightMode + ? R.string.quick_settings_dark_mode_secondary_label_until + : R.string.quick_settings_dark_mode_secondary_label_on_at, + use24HourFormat ? time.toString() : formatter.format(time)); } else { state.secondaryLabel = null; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 27c95552c1d1..50e9a51478ed 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,19 +29,21 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; +import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Icon; import android.media.MediaActionSound; import android.net.Uri; import android.os.AsyncTask; @@ -49,6 +51,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.DisplayMetrics; @@ -60,8 +63,11 @@ import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.animation.Interpolator; +import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; @@ -87,7 +93,7 @@ import dagger.Lazy; * Class for handling device screen shots */ @Singleton -public class GlobalScreenshot { +public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInsetsListener { /** * POD used in the AsyncTask which saves an image in the background. @@ -98,6 +104,7 @@ public class GlobalScreenshot { public Consumer<Uri> finisher; public GlobalScreenshot.ActionsReadyListener mActionsReadyListener; public int errorMsgResId; + public boolean createDeleteAction; void clearImage() { image = null; @@ -106,7 +113,8 @@ public class GlobalScreenshot { } abstract static class ActionsReadyListener { - abstract void onActionsReady(Uri imageUri, List<Notification.Action> actions); + abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions, + List<Notification.Action> actions); } // These strings are used for communicating the action invoked to @@ -130,38 +138,36 @@ public class GlobalScreenshot { private static final int SCREENSHOT_DROP_OUT_DELAY = 500; private static final int SCREENSHOT_DROP_OUT_DURATION = 430; private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370; - private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320; private static final float BACKGROUND_ALPHA = 0.5f; - private static final float SCREENSHOT_SCALE = 1f; - private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f; - private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f; - private static final float SCREENSHOT_CORNER_MIN_SCALE = SCREENSHOT_SCALE * 0.2f; - private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f; - private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f; - private static final float SCREENSHOT_CORNER_MIN_SCALE_OFFSET = .1f; + private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f; + private static final float ROUNDED_CORNER_RADIUS = .05f; private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000; private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; - private Context mContext; - private WindowManager mWindowManager; - private WindowManager.LayoutParams mWindowLayoutParams; - private Display mDisplay; - private DisplayMetrics mDisplayMetrics; + private final Context mContext; + private final WindowManager mWindowManager; + private final WindowManager.LayoutParams mWindowLayoutParams; + private final Display mDisplay; + private final DisplayMetrics mDisplayMetrics; + + private final View mScreenshotLayout; + private final ScreenshotSelectorView mScreenshotSelectorView; + private final ImageView mBackgroundView; + private final ImageView mScreenshotView; + private final ImageView mScreenshotFlash; + private final HorizontalScrollView mActionsContainer; + private final LinearLayout mActionsView; + private final ImageView mBackgroundProtection; private Bitmap mScreenBitmap; - private View mScreenshotLayout; - private ScreenshotSelectorView mScreenshotSelectorView; - private ImageView mBackgroundView; - private ImageView mScreenshotView; - private ImageView mScreenshotFlash; - private LinearLayout mActionsView; - private AnimatorSet mScreenshotAnimation; - private float mBgPadding; - private float mBgPaddingScale; + private float mScreenshotOffsetXPx; + private float mScreenshotOffsetYPx; + private float mScreenshotHeightPx; + private float mCornerScale; private AsyncTask<Void, Void, Void> mSaveInBgTask; @@ -172,7 +178,7 @@ public class GlobalScreenshot { public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: - GlobalScreenshot.this.clearScreenshot(); + GlobalScreenshot.this.clearScreenshot("timeout"); break; default: break; @@ -194,7 +200,20 @@ public class GlobalScreenshot { mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null); mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_background); mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot); + mScreenshotView.setClipToOutline(true); + mScreenshotView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()), + ROUNDED_CORNER_RADIUS * view.getWidth()); + } + }); + + mActionsContainer = mScreenshotLayout.findViewById( + R.id.global_screenshot_actions_container); mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions); + mBackgroundProtection = mScreenshotLayout.findViewById( + R.id.global_screenshot_actions_background); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); @@ -202,6 +221,9 @@ public class GlobalScreenshot { mScreenshotSelectorView.setFocusable(true); mScreenshotSelectorView.setFocusableInTouchMode(true); mScreenshotLayout.setOnTouchListener((v, event) -> { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + clearScreenshot("tap_outside"); + } // Intercept and ignore all touch events return true; }); @@ -212,6 +234,8 @@ public class GlobalScreenshot { WindowManager.LayoutParams.TYPE_SCREENSHOT, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, PixelFormat.TRANSLUCENT); mWindowLayoutParams.setTitle("ScreenshotAnimation"); @@ -222,16 +246,33 @@ public class GlobalScreenshot { mDisplayMetrics = new DisplayMetrics(); mDisplay.getRealMetrics(mDisplayMetrics); - // Scale has to account for both sides of the bg - mBgPadding = (float) resources.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding); - mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; - + mScreenshotOffsetXPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_x); + mScreenshotOffsetYPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_y); + mScreenshotHeightPx = + resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y); + mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale) + / (float) mDisplayMetrics.widthPixels; // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); mCameraSound.load(MediaActionSound.SHUTTER_CLICK); } + @Override // ViewTreeObserver.OnComputeInternalInsetsListener + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + Region touchRegion = new Region(); + + Rect screenshotRect = new Rect(); + mScreenshotView.getBoundsOnScreen(screenshotRect); + touchRegion.op(screenshotRect, Region.Op.UNION); + Rect actionsRect = new Rect(); + mActionsContainer.getBoundsOnScreen(actionsRect); + touchRegion.op(actionsRect, Region.Op.UNION); + + inoutInfo.touchableRegion.set(touchRegion); + } + /** * Creates a new worker thread and saves the screenshot to the media store. */ @@ -241,6 +282,7 @@ public class GlobalScreenshot { data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; + data.createDeleteAction = false; if (mSaveInBgTask != null) { mSaveInBgTask.cancel(false); } @@ -252,6 +294,8 @@ public class GlobalScreenshot { * Takes a screenshot of the current display and shows an animation. */ private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { + clearScreenshot("new screenshot requested"); + int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); @@ -269,6 +313,9 @@ public class GlobalScreenshot { mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + // Start the post-screenshot animation startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels); } @@ -314,12 +361,9 @@ public class GlobalScreenshot { return false; } }); - mScreenshotLayout.post(new Runnable() { - @Override - public void run() { - mScreenshotSelectorView.setVisibility(View.VISIBLE); - mScreenshotSelectorView.requestFocus(); - } + mScreenshotLayout.post(() -> { + mScreenshotSelectorView.setVisibility(View.VISIBLE); + mScreenshotSelectorView.requestFocus(); }); } @@ -337,16 +381,20 @@ public class GlobalScreenshot { /** * Clears current screenshot */ - private void clearScreenshot() { + private void clearScreenshot(String reason) { + Log.e(TAG, "clearing screenshot: " + reason); if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.removeView(mScreenshotLayout); } + mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); + mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); // Clear any references to the bitmap mScreenBitmap = null; mScreenshotView.setImageBitmap(null); - mActionsView.setVisibility(View.GONE); + mActionsContainer.setVisibility(View.GONE); mBackgroundView.setVisibility(View.GONE); + mBackgroundProtection.setAlpha(0f); mScreenshotView.setVisibility(View.GONE); mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); } @@ -374,7 +422,6 @@ public class GlobalScreenshot { mScreenshotAnimation.removeAllListeners(); } - mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h); mScreenshotAnimation = new AnimatorSet(); @@ -385,16 +432,19 @@ public class GlobalScreenshot { // Save the screenshot once we have a bit of time now saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List<Notification.Action> actions) { + void onActionsReady(Uri uri, List<Notification.Action> smartActions, + List<Notification.Action> actions) { if (uri == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { mScreenshotHandler.post(() -> - createScreenshotActionsShadeAnimation(actions).start()); + createScreenshotActionsShadeAnimation(smartActions, + actions).start()); } } }); + mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); mScreenshotHandler.sendMessageDelayed( mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT), SCREENSHOT_CORNER_TIMEOUT_MILLIS); @@ -435,14 +485,6 @@ public class GlobalScreenshot { } }; - Resources r = mContext.getResources(); - if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES) { - mScreenshotView.getBackground().setTint(Color.BLACK); - } else { - mScreenshotView.getBackground().setTintList(null); - } - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(SCREENSHOT_DROP_IN_DURATION); anim.addListener(new AnimatorListenerAdapter() { @@ -453,8 +495,8 @@ public class GlobalScreenshot { mScreenshotView.setAlpha(0f); mScreenshotView.setTranslationX(0f); mScreenshotView.setTranslationY(0f); - mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale); - mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale); + mScreenshotView.setScaleX(1); + mScreenshotView.setScaleY(1); mScreenshotView.setVisibility(View.VISIBLE); mScreenshotFlash.setAlpha(0f); mScreenshotFlash.setVisibility(View.VISIBLE); @@ -469,9 +511,8 @@ public class GlobalScreenshot { @Override public void onAnimationUpdate(ValueAnimator animation) { float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale) - - scaleInterpolator.getInterpolation(t) - * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE); + float scaleT = 1 - (scaleInterpolator.getInterpolation(t) + * (1 - SCREENSHOT_DROP_IN_MIN_SCALE)); mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); mScreenshotView.setAlpha(t); mScreenshotView.setScaleX(scaleT); @@ -500,20 +541,19 @@ public class GlobalScreenshot { }; // Determine the bounds of how to scale - float halfScreenWidth = (w - 2f * mBgPadding) / 2f; - float halfScreenHeight = (h - 2f * mBgPadding) / 2f; - final float offsetPct = SCREENSHOT_CORNER_MIN_SCALE_OFFSET; + float halfScreenWidth = w / 2f; + float halfScreenHeight = h / 2f; final PointF finalPos = new PointF( - -halfScreenWidth + (SCREENSHOT_CORNER_MIN_SCALE + offsetPct) * halfScreenWidth, - halfScreenHeight - (SCREENSHOT_CORNER_MIN_SCALE + offsetPct) * halfScreenHeight); + -halfScreenWidth + mCornerScale * halfScreenWidth + mScreenshotOffsetXPx, + halfScreenHeight - mCornerScale * halfScreenHeight - mScreenshotOffsetYPx); // Animate the screenshot to the bottom left corner anim.setDuration(SCREENSHOT_DROP_OUT_DURATION); anim.addUpdateListener(animation -> { float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) + float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE) - scaleInterpolator.getInterpolation(t) - * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_CORNER_MIN_SCALE); + * (SCREENSHOT_DROP_IN_MIN_SCALE - mCornerScale); mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); mScreenshotView.setScaleX(scaleT); mScreenshotView.setScaleY(scaleT); @@ -523,24 +563,48 @@ public class GlobalScreenshot { return anim; } - private ValueAnimator createScreenshotActionsShadeAnimation(List<Notification.Action> actions) { + private ValueAnimator createScreenshotActionsShadeAnimation( + List<Notification.Action> smartActions, List<Notification.Action> actions) { LayoutInflater inflater = LayoutInflater.from(mContext); mActionsView.removeAllViews(); + mActionsContainer.setScrollX(0); + mScreenshotLayout.invalidate(); + mScreenshotLayout.requestLayout(); + mScreenshotLayout.getViewTreeObserver().dispatchOnGlobalLayout(); + + // By default the activities won't be able to start immediately; override this to keep + // the same behavior as if started from a notification + try { + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + + for (Notification.Action smartAction : smartActions) { + ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + actionChip.setText(smartAction.title); + actionChip.setIcon(smartAction.getIcon(), false); + actionChip.setPendingIntent(smartAction.actionIntent, + () -> clearScreenshot("chip tapped")); + mActionsView.addView(actionChip); + } for (Notification.Action action : actions) { ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); actionChip.setText(action.title); actionChip.setIcon(action.getIcon(), true); - actionChip.setOnClickListener(v -> { - try { - action.actionIntent.send(); - clearScreenshot(); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, - String.format("Intent cancelled (title: %s)", action.title), e); - } - }); + actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped")); + if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) { + mScreenshotView.setOnClickListener(v -> { + try { + action.actionIntent.send(); + clearScreenshot("screenshot preview tapped"); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Intent cancelled", e); + } + }); + } mActionsView.addView(actionChip); } @@ -550,27 +614,29 @@ public class GlobalScreenshot { Toast scrollNotImplemented = Toast.makeText( mContext, "Not implemented", Toast.LENGTH_SHORT); scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate + scrollChip.setIcon( + Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true); scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); mActionsView.addView(scrollChip); } ValueAnimator animator = ValueAnimator.ofFloat(0, 1); - mActionsView.setY(mDisplayMetrics.heightPixels); - mActionsView.setVisibility(VISIBLE); - mActionsView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - float actionsViewHeight = mActionsView.getMeasuredHeight(); - float screenshotStartHeight = mScreenshotView.getTranslationY(); + mActionsContainer.setY(mDisplayMetrics.heightPixels); + mActionsContainer.setVisibility(VISIBLE); + mActionsContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + float actionsViewHeight = mActionsContainer.getMeasuredHeight() + mScreenshotHeightPx; animator.addUpdateListener(animation -> { float t = animation.getAnimatedFraction(); - mScreenshotView.setTranslationY(screenshotStartHeight - actionsViewHeight * t); - mActionsView.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t); + mBackgroundProtection.setAlpha(t); + mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t); }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mScreenshotView.requestFocus(); + mScreenshotView.setElevation(50); } }); return animator; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java index 11aa80bbea35..16447fbdd4aa 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -184,6 +184,7 @@ public class GlobalScreenshotLegacy { data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; + data.createDeleteAction = true; if (mSaveInBgTask != null) { mSaveInBgTask.cancel(false); } @@ -264,12 +265,9 @@ public class GlobalScreenshotLegacy { return false; } }); - mScreenshotLayout.post(new Runnable() { - @Override - public void run() { - mScreenshotSelectorView.setVisibility(View.VISIBLE); - mScreenshotSelectorView.requestFocus(); - } + mScreenshotLayout.post(() -> { + mScreenshotSelectorView.setVisibility(View.VISIBLE); + mScreenshotSelectorView.requestFocus(); }); } @@ -326,8 +324,8 @@ public class GlobalScreenshotLegacy { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); - ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h, - statusBarVisible, navBarVisible); + ValueAnimator screenshotFadeOutAnim = + createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); mScreenshotAnimation = new AnimatorSet(); mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { @@ -336,13 +334,14 @@ public class GlobalScreenshotLegacy { // Save the screenshot once we have a bit of time now saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List<Notification.Action> actions) { + void onActionsReady(Uri uri, List<Notification.Action> smartActions, + List<Notification.Action> actions) { if (uri == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { - mNotificationsController.showScreenshotActionsNotification( - uri, actions); + mNotificationsController + .showScreenshotActionsNotification(uri, smartActions, actions); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 6bad15caf9da..e6082dddd6c7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -65,7 +65,6 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; @@ -90,6 +89,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private final String mScreenshotId; private final boolean mSmartActionsEnabled; + private final boolean mCreateDeleteAction; private final Random mRandom = new Random(); SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { @@ -102,6 +102,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate); mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID()); + mCreateDeleteAction = data.createDeleteAction; + // Initialize screenshot notification smart actions provider. mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false); @@ -196,8 +198,20 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } List<Notification.Action> actions = - populateNotificationActions(mContext, r, uri, smartActionsFuture); - mParams.mActionsReadyListener.onActionsReady(uri, actions); + populateNotificationActions(mContext, r, uri); + List<Notification.Action> smartActions = new ArrayList<>(); + if (mSmartActionsEnabled) { + int timeoutMs = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS, + 1000); + smartActions.addAll(buildSmartActions( + ScreenshotSmartActions.getSmartActions( + mScreenshotId, smartActionsFuture, timeoutMs, + mSmartActionsProvider), + mContext)); + } + mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions); mParams.imageUri = uri; mParams.image = null; mParams.errorMsgResId = 0; @@ -207,7 +221,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Slog.e(TAG, "unable to save screenshot", e); mParams.clearImage(); mParams.errorMsgResId = R.string.screenshot_failed_to_save_text; - mParams.mActionsReadyListener.onActionsReady(null, null); + mParams.mActionsReadyListener.onActionsReady(null, null, null); } // Recycle the bitmap data @@ -228,14 +242,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // If we are cancelled while the task is running in the background, we may get null // params. The finisher is expected to always be called back, so just use the baked-in // params from the ctor in any case. - mParams.mActionsReadyListener.onActionsReady(null, null); + mParams.mActionsReadyListener.onActionsReady(null, null, null); mParams.finisher.accept(null); mParams.clearImage(); } @VisibleForTesting - List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri, - CompletableFuture<List<Notification.Action>> smartActionsFuture) { + List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri) { // Note: Both the share and edit actions are proxied through ActionProxyReceiver in // order to do some common work like dismissing the keyguard and sending // closeSystemWindows @@ -260,6 +273,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); + ArrayList<Notification.Action> actions = new ArrayList<>(); + PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode, new Intent(context, GlobalScreenshot.TargetChosenReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); @@ -282,6 +297,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); + actions.add(shareActionBuilder.build()); // Create an edit intent, if a specific package is provided as the editor, then // launch that directly @@ -309,31 +325,21 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); - - // Create a delete action for the notification - PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) - .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) - .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) - .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, - mSmartActionsEnabled), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); - Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( - Icon.createWithResource(r, R.drawable.ic_screenshot_delete), - r.getString(com.android.internal.R.string.delete), deleteAction); - - ArrayList<Notification.Action> actions = new ArrayList<>( - Arrays.asList(shareActionBuilder.build(), editActionBuilder.build(), - deleteActionBuilder.build())); - if (mSmartActionsEnabled) { - int timeoutMs = DeviceConfig.getInt( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS, - 1000); - actions.addAll(buildSmartActions( - ScreenshotSmartActions.getSmartActions( - mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider), - context)); + actions.add(editActionBuilder.build()); + + if (mCreateDeleteAction) { + // Create a delete action for the notification + PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, + new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) + .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) + .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) + .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, + mSmartActionsEnabled), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); + Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( + Icon.createWithResource(r, R.drawable.ic_screenshot_delete), + r.getString(com.android.internal.R.string.delete), deleteAction); + actions.add(deleteActionBuilder.build()); } return actions; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java index 6edacd12a9d1..44b20e535cce 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java @@ -17,9 +17,11 @@ package com.android.systemui.screenshot; import android.annotation.ColorInt; +import android.app.PendingIntent; import android.content.Context; import android.graphics.drawable.Icon; import android.util.AttributeSet; +import android.util.Log; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -31,6 +33,8 @@ import com.android.systemui.R; */ public class ScreenshotActionChip extends LinearLayout { + private static final String TAG = "ScreenshotActionChip"; + private ImageView mIcon; private TextView mText; private @ColorInt int mIconColor; @@ -47,11 +51,11 @@ public class ScreenshotActionChip extends LinearLayout { this(context, attrs, defStyleAttr, 0); } - public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + public ScreenshotActionChip( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mIconColor = context.getColor(R.color.global_screenshot_button_text); + mIconColor = context.getColor(R.color.global_screenshot_button_icon); } @Override @@ -70,4 +74,15 @@ public class ScreenshotActionChip extends LinearLayout { void setText(CharSequence text) { mText.setText(text); } + + void setPendingIntent(PendingIntent intent, Runnable finisher) { + setOnClickListener(v -> { + try { + intent.send(); + finisher.run(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Intent cancelled", e); + } + }); + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 42fca9413afd..811a8d936b77 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -190,10 +190,14 @@ public class ScreenshotNotificationsController { */ public void showScreenshotActionsNotification( Uri imageUri, + List<Notification.Action> smartActions, List<Notification.Action> actions) { for (Notification.Action action : actions) { mNotificationBuilder.addAction(action); } + for (Notification.Action smartAction : smartActions) { + mNotificationBuilder.addAction(smartAction); + } // Create the intent to show the screenshot in gallery Intent launchIntent = new Intent(Intent.ACTION_VIEW); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 4f045d5c1b71..9570b5a3b57c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -107,6 +107,7 @@ public class TakeScreenshotService extends Service { @Override public boolean onUnbind(Intent intent) { if (mScreenshot != null) mScreenshot.stopScreenshot(); + // TODO (mkephart) remove once notifications flow is fully deprecated if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot(); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt new file mode 100644 index 000000000000..083fbc92dc68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import android.app.ActivityManager +import android.content.res.Resources +import android.os.SystemProperties +import android.util.MathUtils +import android.view.SurfaceControl +import android.view.ViewRootImpl +import com.android.internal.util.IndentingPrintWriter +import com.android.systemui.DumpController +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Main +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class BlurUtils @Inject constructor( + @Main private val resources: Resources, + val dumpController: DumpController +) : Dumpable { + private val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius) + private val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius) + private val blurSysProp = SystemProperties + .getBoolean("ro.surface_flinger.supports_background_blur", false) + + init { + dumpController.registerDumpable(this) + } + + /** + * Translates a ratio from 0 to 1 to a blur radius in pixels. + */ + fun radiusForRatio(ratio: Float): Int { + if (ratio == 0f) { + return 0 + } + return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio).toInt() + } + + /** + * Applies background blurs to a {@link ViewRootImpl}. + * + * @param viewRootImpl The window root. + * @param radius blur radius in pixels. + */ + fun applyBlur(viewRootImpl: ViewRootImpl?, radius: Int) { + if (viewRootImpl == null || !supportsBlursOnWindows()) { + return + } + SurfaceControl.Transaction().use { + it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius) + it.apply() + } + } + + /** + * If this device can render blurs. + * + * @see android.view.SurfaceControl.Transaction#setBackgroundBlurRadius(SurfaceControl, int) + * @return {@code true} when supported. + */ + fun supportsBlursOnWindows(): Boolean { + return blurSysProp && ActivityManager.isHighEndGfx() + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + IndentingPrintWriter(pw, " ").use { + it.println("BlurUtils:") + it.increaseIndent() + it.println("minBlurRadius: $minBlurRadius") + it.println("maxBlurRadius: $maxBlurRadius") + it.println("blurSysProp: $blurSysProp") + it.println("supportsBlursOnWindows: ${supportsBlursOnWindows()}") + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWindowBlurController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWindowBlurController.kt new file mode 100644 index 000000000000..2e72163f1840 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWindowBlurController.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import android.content.res.Resources +import android.view.View +import com.android.internal.util.IndentingPrintWriter +import com.android.systemui.DumpController +import com.android.systemui.Dumpable +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.phone.PanelExpansionListener +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Controller responsible for statusbar window blur. + */ +@Singleton +class StatusBarWindowBlurController @Inject constructor( + @Main private val resources: Resources, + private val statusBarStateController: SysuiStatusBarStateController, + private val blurUtils: BlurUtils, + dumpController: DumpController +) : PanelExpansionListener, Dumpable { + + lateinit var root: View + private var blurRadius = 0 + + init { + dumpController.registerDumpable(this) + } + + override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) { + val newBlur = if (statusBarStateController.state == StatusBarState.SHADE) + blurUtils.radiusForRatio(expansion) + else + 0 + + if (blurRadius == newBlur) { + return + } + blurRadius = newBlur + blurUtils.applyBlur(root.viewRootImpl, blurRadius) + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + IndentingPrintWriter(pw, " ").use { + it.println("StatusBarWindowBlurController:") + it.increaseIndent() + it.println("blurRadius: $blurRadius") + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 9840a7ba90de..ef581db0b053 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -31,7 +31,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; -import android.util.StatsLog; import android.view.ISystemGestureExclusionListener; import android.view.InputChannel; import android.view.InputDevice; @@ -54,6 +53,7 @@ import com.android.systemui.plugins.PluginListener; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.SysUiStatsLog; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -133,23 +133,23 @@ public class EdgeBackGestureHandler implements DisplayListener, mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); int backtype = (mInRejectedExclusion - ? StatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED : - StatsLog.BACK_GESTURE__TYPE__COMPLETED); - StatsLog.write(StatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype, + ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED : + SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED); + SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype, (int) mDownPoint.y, mIsOnLeftEdge - ? StatsLog.BACK_GESTURE__X_LOCATION__LEFT : - StatsLog.BACK_GESTURE__X_LOCATION__RIGHT); + ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT : + SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT); } @Override public void cancelBack() { mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x, (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); - int backtype = StatsLog.BACK_GESTURE__TYPE__INCOMPLETE; - StatsLog.write(StatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype, + int backtype = SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE; + SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype, (int) mDownPoint.y, mIsOnLeftEdge - ? StatsLog.BACK_GESTURE__X_LOCATION__LEFT : - StatsLog.BACK_GESTURE__X_LOCATION__RIGHT); + ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT : + SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT); } }; @@ -334,10 +334,10 @@ public class EdgeBackGestureHandler implements DisplayListener, if (isInExcludedRegion) { mOverviewProxyService.notifyBackAction(false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge); - StatsLog.write(StatsLog.BACK_GESTURE_REPORTED_REPORTED, - StatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED, y, - mIsOnLeftEdge ? StatsLog.BACK_GESTURE__X_LOCATION__LEFT : - StatsLog.BACK_GESTURE__X_LOCATION__RIGHT); + SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, + SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED, y, + mIsOnLeftEdge ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT : + SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT); } else { mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index f34c15c7099d..3074e33c46a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -27,7 +27,6 @@ import android.os.UserManager; import android.util.Log; import android.util.MathUtils; import android.util.Slog; -import android.util.StatsLog; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -47,6 +46,7 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.PrintWriter; @@ -248,8 +248,8 @@ public class KeyguardBouncer { mKeyguardView.onResume(); mKeyguardView.resetSecurityContainer(); } - StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, - StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN); } }; @@ -290,8 +290,8 @@ public class KeyguardBouncer { public void hide(boolean destroyView) { if (isShowing()) { - StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, - StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN); mDismissCallbackRegistry.notifyDismissCancelled(); } mIsScrimmed = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index c691a35b566a..ab1c8ade7001 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import android.annotation.Nullable; import android.app.StatusBarManager; import android.graphics.RectF; import android.hardware.display.AmbientDisplayConfiguration; @@ -44,6 +45,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.PulseExpansionHandler; +import com.android.systemui.statusbar.StatusBarWindowBlurController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -79,6 +81,7 @@ public class NotificationShadeWindowViewController { private final CommandQueue mCommandQueue; private final NotificationShadeWindowView mView; private final ShadeController mShadeController; + private final StatusBarWindowBlurController mBlurController; private GestureDetector mGestureDetector; private View mBrightnessMirror; @@ -120,6 +123,7 @@ public class NotificationShadeWindowViewController { CommandQueue commandQueue, ShadeController shadeController, DockManager dockManager, + @Nullable StatusBarWindowBlurController blurController, NotificationShadeWindowView statusBarWindowView, NotificationPanelViewController notificationPanelViewController) { mInjectionInflationController = injectionInflationController; @@ -141,6 +145,7 @@ public class NotificationShadeWindowViewController { mShadeController = shadeController; mDockManager = dockManager; mNotificationPanelViewController = notificationPanelViewController; + mBlurController = blurController; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror); @@ -383,6 +388,11 @@ public class NotificationShadeWindowViewController { new DragDownHelper( mView.getContext(), mView, expandHelperCallback, dragDownCallback, mFalsingManager)); + + if (mBlurController != null) { + mBlurController.setRoot(mView); + mNotificationPanelViewController.addExpansionListener(mBlurController); + } } public NotificationShadeWindowView getView() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index de37cd955f9a..86a81ce3d1f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.res.ColorStateList; import android.os.Bundle; import android.os.SystemClock; -import android.util.StatsLog; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -48,6 +47,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.RemoteInputController; @@ -319,8 +319,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardStateController.notifyKeyguardState(mShowing, mKeyguardStateController.isOccluded()); reset(true /* hideBouncerWhenShowing */); - StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED, - StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); } /** @@ -501,8 +501,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void setOccluded(boolean occluded, boolean animate) { mStatusBar.setOccluded(occluded); if (occluded && !mOccluded && mShowing) { - StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED, - StatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED); if (mStatusBar.isInLaunchTransition()) { mOccluded = true; mStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */, @@ -516,8 +516,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return; } } else if (!occluded && mOccluded && mShowing) { - StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED, - StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); } boolean isOccluding = !mOccluded && occluded; mOccluded = occluded; @@ -653,8 +653,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mNotificationShadeWindowController.setKeyguardShowing(false); mViewMediatorCallback.keyguardGone(); } - StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED, - StatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN); + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, + SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN); } private boolean needsBypassFading() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index 7cf5147a9f26..fb30bdec68b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -121,8 +121,13 @@ public class StatusBarWindowController { apply(mCurrentState); } + private void applyHeight() { + mLpChanged.height = mBarHeight; + } + private void apply(State state) { applyForceStatusBarVisibleFlag(state); + applyHeight(); if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { mWindowManager.updateViewLayout(mStatusBarView, mLp); } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt new file mode 100644 index 000000000000..ecd3afd687b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.animation + +import android.graphics.Rect +import android.graphics.RectF +import androidx.dynamicanimation.animation.FloatPropertyCompat + +/** + * Helpful extra properties to use with the [PhysicsAnimator]. These allow you to animate objects + * such as [Rect] and [RectF]. + * + * There are additional, more basic properties available in [DynamicAnimation]. + */ +class FloatProperties { + companion object { + /** + * Represents the x-coordinate of a [Rect]. Typically used to animate moving a Rect + * horizontally. + * + * This property's getter returns [Rect.left], and its setter uses [Rect.offsetTo], which + * sets [Rect.left] to the new value and offsets [Rect.right] so that the width of the Rect + * does not change. + */ + @JvmField + val RECT_X = object : FloatPropertyCompat<Rect>("RectX") { + override fun setValue(rect: Rect?, value: Float) { + rect?.offsetTo(value.toInt(), rect.top) + } + + override fun getValue(rect: Rect?): Float { + return rect?.left?.toFloat() ?: -Float.MAX_VALUE + } + } + + /** + * Represents the y-coordinate of a [Rect]. Typically used to animate moving a Rect + * vertically. + * + * This property's getter returns [Rect.top], and its setter uses [Rect.offsetTo], which + * sets [Rect.top] to the new value and offsets [Rect.bottom] so that the height of the Rect + * does not change. + */ + @JvmField + val RECT_Y = object : FloatPropertyCompat<Rect>("RectY") { + override fun setValue(rect: Rect?, value: Float) { + rect?.offsetTo(rect.left, value.toInt()) + } + + override fun getValue(rect: Rect?): Float { + return rect?.top?.toFloat() ?: -Float.MAX_VALUE + } + } + + /** + * Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF + * horizontally. + * + * This property's getter returns [RectF.left], and its setter uses [RectF.offsetTo], which + * sets [RectF.left] to the new value and offsets [RectF.right] so that the width of the + * RectF does not change. + */ + @JvmField + val RECTF_X = object : FloatPropertyCompat<RectF>("RectFX") { + override fun setValue(rect: RectF?, value: Float) { + rect?.offsetTo(value, rect.top) + } + + override fun getValue(rect: RectF?): Float { + return rect?.left ?: -Float.MAX_VALUE + } + } + + /** + * Represents the y-coordinate of a [RectF]. Typically used to animate moving a RectF + * vertically. + * + * This property's getter returns [RectF.top], and its setter uses [RectF.offsetTo], which + * sets [RectF.top] to the new value and offsets [RectF.bottom] so that the height of the + * RectF does not change. + */ + @JvmField + val RECTF_Y = object : FloatPropertyCompat<RectF>("RectFY") { + override fun setValue(rect: RectF?, value: Float) { + rect?.offsetTo(rect.left, value) + } + + override fun getValue(rect: RectF?): Float { + return rect?.top ?: -Float.MAX_VALUE + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index 8a1759d4d0cd..cfd77be9303d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -293,15 +293,19 @@ class PhysicsAnimator<T> private constructor (val target: T) { val velocityToReachDestination = distanceToDestination * (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER) - // Try to use the provided start velocity, but use the required velocity to reach the - // destination if the provided velocity is insufficient. - val sufficientVelocity = - if (distanceToDestination < 0) - min(velocityToReachDestination, startVelocity) - else - max(velocityToReachDestination, startVelocity) - - flingConfigCopy.startVelocity = sufficientVelocity + // If there's distance to cover, and the provided velocity is moving in the correct + // direction, ensure that the velocity is high enough to reach the destination. + // Otherwise, just use startVelocity - this means that the fling is at or out of bounds. + // The fling will immediately end and a spring will bring the object back into bounds + // with this startVelocity. + flingConfigCopy.startVelocity = when { + distanceToDestination > 0f && startVelocity >= 0f -> + max(velocityToReachDestination, startVelocity) + distanceToDestination < 0f && startVelocity <= 0f -> + min(velocityToReachDestination, startVelocity) + else -> startVelocity + } + springConfigCopy.finalPosition = toAtLeast } else { flingConfigCopy.startVelocity = startVelocity @@ -367,8 +371,17 @@ class PhysicsAnimator<T> private constructor (val target: T) { * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param, * which indicates that all relevant animations have ended. */ - fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> { - this.endActions.addAll(endActions) + fun withEndActions(vararg endActions: EndAction?): PhysicsAnimator<T> { + this.endActions.addAll(endActions.filterNotNull()) + return this + } + + /** + * Helper overload so that callers from Java can use Runnables or method references as end + * actions without having to explicitly return Unit. + */ + fun withEndActions(vararg endActions: Runnable?): PhysicsAnimator<T> { + this.endActions.addAll(endActions.filterNotNull().map { it::run }) return this } @@ -416,8 +429,10 @@ class PhysicsAnimator<T> private constructor (val target: T) { max = max(currentValue, this.max) } - // Apply the configuration and start the animation. + // Apply the configuration and start the animation. Since flings can't be + // redirected while in motion, cancel it first. getFlingAnimation(animatedProperty) + .also { it.cancel() } .also { flingConfig.applyToAnimation(it) } .start() } diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java index 02c7857f0cfd..5aba013a7fb8 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java @@ -39,10 +39,10 @@ import android.view.IWindowSessionCallback; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.view.SurfaceControlViewHost; import android.view.WindowlessWindowManager; import com.android.internal.os.IResultReceiver; @@ -238,11 +238,13 @@ public class SystemWindows { long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, viewVisibility, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, - cutout, mergedConfiguration, outSurfaceControl, outInsetsState); + cutout, mergedConfiguration, outSurfaceControl, outInsetsState, + outSurfaceSize); if (res != 0) { return res; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 2c7cee3e2be1..aeb31e11be23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -169,11 +169,11 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; + data.createDeleteAction = true; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); List<Notification.Action> actions = task.populateNotificationActions( mContext, mContext.getResources(), - Uri.parse("Screenshot_123.png"), - CompletableFuture.completedFuture(Collections.emptyList())); + Uri.parse("Screenshot_123.png")); Assert.assertEquals(actions.size(), 3); boolean isShareFound = false; @@ -184,7 +184,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { Assert.assertNotNull(intent); Bundle bundle = intent.getExtras(); Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); - Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + Assert.assertTrue( + bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); if (action.title.equals(GlobalScreenshot.ACTION_TYPE_DELETE)) { isDeleteFound = intent.getAction() == null; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 985354007e25..8936a2dcab7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.PulseExpansionHandler; +import com.android.systemui.statusbar.StatusBarWindowBlurController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -79,6 +80,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private DockManager mDockManager; @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; + @Mock private StatusBarWindowBlurController mStatusBarWindowBlurController; @Before public void setUp() { @@ -112,6 +114,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { new CommandQueue(mContext), mShadeController, mDockManager, + mStatusBarWindowBlurController, mView, mNotificationPanelViewController); mController.setupExpandedStatusBar(); diff --git a/services/Android.bp b/services/Android.bp index 914ea2859fb1..a58245361939 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -6,7 +6,7 @@ filegroup { } filegroup { - name: "services-stub-sources", + name: "services-all-sources", srcs: [ ":services.core-sources", ":services.accessibility-sources", @@ -111,7 +111,7 @@ filegroup { droidstubs { name: "services-stubs.sources", - srcs: [":services-stub-sources"], + srcs: [":services-all-sources"], installable: false, // TODO: remove the --hide options below args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" + diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index bda0e3bb39fc..5e10916c4491 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -47,6 +47,7 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.WorkSource; +import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -65,14 +66,18 @@ import com.android.server.backup.remote.RemoteCall; import com.android.server.backup.remote.RemoteCallable; import com.android.server.backup.remote.RemoteResult; import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.utils.AppBackupUtils; +import libcore.io.IoUtils; + import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.annotation.Retention; @@ -80,8 +85,10 @@ import java.lang.annotation.RetentionPolicy; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -169,10 +176,14 @@ import java.util.concurrent.atomic.AtomicInteger; // TODO: Consider having the caller responsible for some clean-up (like resetting state) // TODO: Distinguish between cancel and time-out where possible for logging/monitoring/observing public class KeyValueBackupTask implements BackupRestoreTask, Runnable { + private static final String TAG = "KVBT"; + private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_BACKGROUND; private static final AtomicInteger THREAD_COUNT = new AtomicInteger(); private static final String BLANK_STATE_FILE_NAME = "blank_state"; private static final String PM_PACKAGE = UserBackupManagerService.PACKAGE_MANAGER_SENTINEL; + private static final String SUCCESS_STATE_SUBDIR = "backing-up"; + @VisibleForTesting static final String NO_DATA_END_SENTINEL = "@end@"; @VisibleForTesting public static final String STAGING_FILE_SUFFIX = ".data"; @VisibleForTesting public static final String NEW_STATE_FILE_SUFFIX = ".new"; @@ -336,6 +347,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mHasDataToBackup = false; + Set<String> backedUpApps = new HashSet<>(); int status = BackupTransport.TRANSPORT_OK; try { startTask(); @@ -347,13 +359,18 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } else { backupPackage(packageName); } + setSuccessState(packageName, true); + backedUpApps.add(packageName); } catch (AgentException e) { + setSuccessState(packageName, false); if (e.isTransitory()) { // We try again this package in the next backup pass. mBackupManagerService.dataChangedImpl(packageName); } } } + + informTransportOfUnchangedApps(backedUpApps); } catch (TaskException e) { if (e.isStateCompromised()) { mBackupManagerService.resetBackupState(mStateDirectory); @@ -364,6 +381,185 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { finishTask(status); } + /** + * Tell the transport about all of the packages which have successfully backed up but + * have not informed the framework that they have new data. This allows transports to + * differentiate between packages which are not backing data up due to an error and + * packages which are not backing up data because nothing has changed. + * + * The current implementation involves creating a state file when a backup succeeds, + * on subsequent runs the existence of the file indicates the backup ran successfully + * but there was no data. If a backup fails with an error, or if the package is not + * eligible for backup by the transport any more, the status file is removed and the + * "no data" message will not be sent to the transport until another successful data + * changed backup has succeeded. + * + * @param appsBackedUp The Set of apps backed up during this run so we can exclude them + * from the list of successfully backed up apps that we signal to + * the transport have no data. + */ + private void informTransportOfUnchangedApps(Set<String> appsBackedUp) { + String[] succeedingPackages = getSucceedingPackages(); + if (succeedingPackages == null) { + // Nothing is succeeding, so end early. + return; + } + + int flags = BackupTransport.FLAG_DATA_NOT_CHANGED; + if (mUserInitiated) { + flags |= BackupTransport.FLAG_USER_INITIATED; + } + + boolean noDataPackageEncountered = false; + try { + IBackupTransport transport = + mTransportClient.connectOrThrow("KVBT.informTransportOfEmptyBackups()"); + + for (String packageName : succeedingPackages) { + if (appsBackedUp.contains(packageName)) { + Log.v(TAG, "Skipping package which was backed up this time :" + packageName); + // Skip packages we backed up in this run. + continue; + } + + PackageInfo packageInfo; + try { + packageInfo = mPackageManager.getPackageInfo(packageName, /* flags */ 0); + if (!isEligibleForNoDataCall(packageInfo)) { + // If the package isn't eligible any more we can forget about it and move + // on. + clearStatus(packageName); + continue; + } + } catch (PackageManager.NameNotFoundException e) { + // If the package has been uninstalled we can forget about it and move on. + clearStatus(packageName); + continue; + } + + sendNoDataChangedTo(transport, packageInfo, flags); + noDataPackageEncountered = true; + } + + if (noDataPackageEncountered) { + // If we've notified the transport of an unchanged package we need to + // tell it that it's seen all of the unchanged packages. We do this by + // reporting the end sentinel package as unchanged. + PackageInfo endSentinal = new PackageInfo(); + endSentinal.packageName = NO_DATA_END_SENTINEL; + sendNoDataChangedTo(transport, endSentinal, flags); + } + } catch (TransportNotAvailableException | RemoteException e) { + Log.e(TAG, "Could not inform transport of all unchanged apps", e); + } + } + + /** Determine if a package is eligible to be backed up to the transport */ + private boolean isEligibleForNoDataCall(PackageInfo packageInfo) { + return AppBackupUtils.appIsKeyValueOnly(packageInfo) + && AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport(mTransportClient, + packageInfo.packageName, mPackageManager, mUserId); + } + + /** Send the "no data changed" message to a transport for a specific package */ + private void sendNoDataChangedTo(IBackupTransport transport, PackageInfo packageInfo, int flags) + throws RemoteException { + ParcelFileDescriptor pfd; + try { + pfd = ParcelFileDescriptor.open(mBlankStateFile, MODE_READ_ONLY | MODE_CREATE); + } catch (FileNotFoundException e) { + Log.e(TAG, "Unable to find blank state file, aborting unchanged apps signal."); + return; + } + try { + int result = transport.performBackup(packageInfo, pfd, flags); + if (result == BackupTransport.TRANSPORT_ERROR + || result == BackupTransport.TRANSPORT_NOT_INITIALIZED) { + Log.w( + TAG, + "Aborting informing transport of unchanged apps, transport" + " errored"); + return; + } + + transport.finishBackup(); + } finally { + IoUtils.closeQuietly(pfd); + } + } + + /** Get the list of package names which are marked as having previously succeeded */ + private String[] getSucceedingPackages() { + File stateDirectory = getTopLevelSuccessStateDirectory(/* createIfMissing */ false); + if (stateDirectory == null) { + // getSuccessStateFileFor logs when we can't use the state area + return null; + } + + return stateDirectory.list(); + } + + /** Sets the indicator that a package backup is succeeding */ + private void setSuccessState(String packageName, boolean success) { + File successStateFile = getSuccessStateFileFor(packageName); + if (successStateFile == null) { + // The error will have been logged by getSuccessStateFileFor(). + return; + } + + if (successStateFile.exists() != success) { + // If there's been a change of state + if (!success) { + // Clear the status if we're now failing + clearStatus(packageName, successStateFile); + return; + } + + // For succeeding packages we want the file + try { + if (!successStateFile.createNewFile()) { + Log.w(TAG, "Unable to permanently record success for " + packageName); + } + } catch (IOException e) { + Log.w(TAG, "Unable to permanently record success for " + packageName, e); + } + } + } + + /** Clear the status file for a specific package */ + private void clearStatus(String packageName) { + File successStateFile = getSuccessStateFileFor(packageName); + if (successStateFile == null) { + // The error will have been logged by getSuccessStateFileFor(). + return; + } + clearStatus(packageName, successStateFile); + } + + /** Clear the status file for a package once we have the File representation */ + private void clearStatus(String packageName, File successStateFile) { + if (successStateFile.exists()) { + if (!successStateFile.delete()) { + Log.w(TAG, "Unable to remove status file for " + packageName); + } + } + } + + /** Get the backup state file for a package **/ + private File getSuccessStateFileFor(String packageName) { + File stateDirectory = getTopLevelSuccessStateDirectory(/* createIfMissing */ true); + return stateDirectory == null ? null : new File(stateDirectory, packageName); + } + + /** The top level directory for success state files */ + private File getTopLevelSuccessStateDirectory(boolean createIfMissing) { + File directory = new File(mStateDirectory, SUCCESS_STATE_SUBDIR); + if (!directory.exists() && createIfMissing && !directory.mkdirs()) { + Log.e(TAG, "Unable to create backing-up state directory"); + return null; + } + return directory; + } + /** Returns transport status. */ private int sendDataToTransport(@Nullable PackageInfo packageInfo) throws AgentException, TaskException { diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java index d84197c749b6..aedafbbf1662 100644 --- a/services/core/java/android/os/UserManagerInternal.java +++ b/services/core/java/android/os/UserManagerInternal.java @@ -162,7 +162,8 @@ public abstract class UserManagerInternal { * createAndManageUser is called by the device owner. */ public abstract UserInfo createUserEvenWhenDisallowed(String name, String userType, - int flags, String[] disallowedPackages); + int flags, String[] disallowedPackages) + throws UserManager.CheckedUserOperationException; /** * Same as {@link UserManager#removeUser(int userId)}, but bypasses the check for diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 98b572801716..7ab345387fab 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5801,6 +5801,19 @@ public class ConnectivityService extends IConnectivityManager.Stub return INetd.PERMISSION_NONE; } + private void updateNetworkPermissions(@NonNull final NetworkAgentInfo nai, + @NonNull final NetworkCapabilities newNc) { + final int oldPermission = getNetworkPermission(nai.networkCapabilities); + final int newPermission = getNetworkPermission(newNc); + if (oldPermission != newPermission && nai.created && !nai.isVPN()) { + try { + mNMS.setNetworkPermission(nai.network.netId, newPermission); + } catch (RemoteException e) { + loge("Exception in setNetworkPermission: " + e); + } + } + } + /** * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal, @@ -5873,21 +5886,11 @@ public class ConnectivityService extends IConnectivityManager.Stub * @param nai the network having its capabilities updated. * @param nc the new network capabilities. */ - private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) { + private void updateCapabilities(final int oldScore, @NonNull final NetworkAgentInfo nai, + @NonNull final NetworkCapabilities nc) { NetworkCapabilities newNc = mixInCapabilities(nai, nc); - if (Objects.equals(nai.networkCapabilities, newNc)) return; - - final int oldPermission = getNetworkPermission(nai.networkCapabilities); - final int newPermission = getNetworkPermission(newNc); - if (oldPermission != newPermission && nai.created && !nai.isVPN()) { - try { - mNMS.setNetworkPermission(nai.network.netId, newPermission); - } catch (RemoteException e) { - loge("Exception in setNetworkPermission: " + e); - } - } - + updateNetworkPermissions(nai, newNc); final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc); updateUids(nai, prevNc, newNc); @@ -6268,6 +6271,30 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // An accumulator class to gather the list of changes that result from a rematch. + // TODO : enrich to represent an entire set of changes to apply. + private static class NetworkReassignment { + static class NetworkBgStatePair { + @NonNull final NetworkAgentInfo mNetwork; + final boolean mOldBackground; + NetworkBgStatePair(@NonNull final NetworkAgentInfo network, + final boolean oldBackground) { + mNetwork = network; + mOldBackground = oldBackground; + } + } + + @NonNull private final Set<NetworkBgStatePair> mRematchedNetworks = new ArraySet<>(); + + @NonNull Iterable<NetworkBgStatePair> getRematchedNetworks() { + return mRematchedNetworks; + } + + void addRematchedNetwork(@NonNull final NetworkBgStatePair network) { + mRematchedNetworks.add(network); + } + } + private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork( @NonNull final NetworkAgentInfo newNetwork) { final int score = newNetwork.getCurrentScore(); @@ -6313,8 +6340,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // needed. A network is needed if it is the best network for // one or more NetworkRequests, or if it is a VPN. // - // - Tears down newNetwork if it just became validated - // but turns out to be unneeded. + // - Writes into the passed reassignment object all changes that should be done for + // rematching this network with all requests, to be applied later. // // NOTE: This function only adds NetworkRequests that "newNetwork" could satisfy, // it does not remove NetworkRequests that other Networks could better satisfy. @@ -6322,15 +6349,22 @@ public class ConnectivityService extends IConnectivityManager.Stub // This function should be used when possible instead of {@code rematchAllNetworksAndRequests} // as it performs better by a factor of the number of Networks. // + // TODO : stop writing to the passed reassignment. This is temporarily more useful, but + // it's unidiomatic Java and it's hard to read. + // + // @param changes a currently-building list of changes to write to // @param newNetwork is the network to be matched against NetworkRequests. // @param now the time the rematch starts, as returned by SystemClock.elapsedRealtime(); - private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, long now) { + private void rematchNetworkAndRequests(@NonNull final NetworkReassignment changes, + @NonNull final NetworkAgentInfo newNetwork, final long now) { ensureRunningOnConnectivityServiceThread(); if (!newNetwork.everConnected) return; boolean isNewDefault = false; NetworkAgentInfo oldDefaultNetwork = null; - final boolean wasBackgroundNetwork = newNetwork.isBackgroundNetwork(); + changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(newNetwork, + newNetwork.isBackgroundNetwork())); + final int score = newNetwork.getCurrentScore(); if (VDBG || DDBG) log("rematching " + newNetwork.name()); @@ -6433,39 +6467,12 @@ public class ConnectivityService extends IConnectivityManager.Stub if (newNetwork.getCurrentScore() != score) { Slog.wtf(TAG, String.format( "BUG: %s changed score during rematch: %d -> %d", - newNetwork.name(), score, newNetwork.getCurrentScore())); + newNetwork.name(), score, newNetwork.getCurrentScore())); } // Notify requested networks are available after the default net is switched, but // before LegacyTypeTracker sends legacy broadcasts for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); - - // Finally, process listen requests and update capabilities if the background state has - // changed for this network. For consistency with previous behavior, send onLost callbacks - // before onAvailable. - processNewlyLostListenRequests(newNetwork); - - // Maybe the network changed background states. Update its capabilities. - final boolean backgroundChanged = wasBackgroundNetwork != newNetwork.isBackgroundNetwork(); - if (backgroundChanged) { - final NetworkCapabilities newNc = mixInCapabilities(newNetwork, - newNetwork.networkCapabilities); - - final int oldPermission = getNetworkPermission(newNetwork.networkCapabilities); - final int newPermission = getNetworkPermission(newNc); - if (oldPermission != newPermission) { - try { - mNMS.setNetworkPermission(newNetwork.network.netId, newPermission); - } catch (RemoteException e) { - loge("Exception in setNetworkPermission: " + e); - } - } - - newNetwork.getAndSetNetworkCapabilities(newNc); - notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED); - } - - processNewlySatisfiedListenRequests(newNetwork); } /** @@ -6487,12 +6494,24 @@ public class ConnectivityService extends IConnectivityManager.Stub // scoring network and then a higher scoring network, which could produce multiple // callbacks. Arrays.sort(nais); + final NetworkReassignment changes = new NetworkReassignment(); for (final NetworkAgentInfo nai : nais) { - rematchNetworkAndRequests(nai, now); + rematchNetworkAndRequests(changes, nai, now); } final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork(); + for (final NetworkReassignment.NetworkBgStatePair event : changes.getRematchedNetworks()) { + // Process listen requests and update capabilities if the background state has + // changed for this network. For consistency with previous behavior, send onLost + // callbacks before onAvailable. + processNewlyLostListenRequests(event.mNetwork); + if (event.mOldBackground != event.mNetwork.isBackgroundNetwork()) { + applyBackgroundChangeForRematch(event.mNetwork); + } + processNewlySatisfiedListenRequests(event.mNetwork); + } + for (final NetworkAgentInfo nai : nais) { // Rematching may have altered the linger state of some networks, so update all linger // timers. updateLingerState reads the state from the network agent and does nothing @@ -6524,6 +6543,24 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Apply a change in background state resulting from rematching networks with requests. + * + * During rematch, a network may change background states by starting to satisfy or stopping + * to satisfy a foreground request. Listens don't count for this. When a network changes + * background states, its capabilities need to be updated and callbacks fired for the + * capability change. + * + * @param nai The network that changed background states + */ + private void applyBackgroundChangeForRematch(@NonNull final NetworkAgentInfo nai) { + final NetworkCapabilities newNc = mixInCapabilities(nai, nai.networkCapabilities); + if (Objects.equals(nai.networkCapabilities, newNc)) return; + updateNetworkPermissions(nai, newNc); + nai.getAndSetNetworkCapabilities(newNc); + notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); + } + private void updateLegacyTypeTrackerAndVpnLockdownForRematch( @Nullable final NetworkAgentInfo oldDefaultNetwork, @Nullable final NetworkAgentInfo newDefaultNetwork, diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java index 1ff455ea7a8f..c34dd984f865 100644 --- a/services/core/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java @@ -16,15 +16,273 @@ package com.android.server; -import android.os.IBinder; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.timedetector.NetworkTimeSuggestion; +import android.app.timedetector.TimeDetector; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.os.TimestampedValue; +import android.provider.Settings; +import android.util.Log; +import android.util.NtpTrustedTime; +import android.util.TimeUtils; + +import com.android.internal.util.DumpUtils; + +import java.io.FileDescriptor; +import java.io.PrintWriter; /** - * An interface for NetworkTimeUpdateService implementations. Eventually part or all of this service - * will be subsumed into {@link com.android.server.timedetector.TimeDetectorService}. In the - * meantime this interface allows Android to use either the old or new implementation. + * Monitors the network time. If looking up the network time fails for some reason, it tries a few + * times with a short interval and then resets to checking on longer intervals. + * + * <p>When available, the time is always suggested to the {@link + * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device + * system clock, depending on user settings and what other signals are available. */ -public interface NetworkTimeUpdateService extends IBinder { +public class NetworkTimeUpdateService extends Binder { + + private static final String TAG = "NetworkTimeUpdateService"; + private static final boolean DBG = false; + + private static final int EVENT_AUTO_TIME_ENABLED = 1; + private static final int EVENT_POLL_NETWORK_TIME = 2; + private static final int EVENT_NETWORK_CHANGED = 3; + + private static final String ACTION_POLL = + "com.android.server.NetworkTimeUpdateService.action.POLL"; + + private static final int POLL_REQUEST = 0; + + private Network mDefaultNetwork = null; + + private final Context mContext; + private final NtpTrustedTime mTime; + private final AlarmManager mAlarmManager; + private final TimeDetector mTimeDetector; + private final ConnectivityManager mCM; + private final PendingIntent mPendingPollIntent; + private final PowerManager.WakeLock mWakeLock; + + // NTP lookup is done on this thread and handler + private Handler mHandler; + private AutoTimeSettingObserver mAutoTimeSettingObserver; + private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback; + + // Normal polling frequency + private final long mPollingIntervalMs; + // Try-again polling interval, in case the network request failed + private final long mPollingIntervalShorterMs; + // Number of times to try again + private final int mTryAgainTimesMax; + // Keeps track of how many quick attempts were made to fetch NTP time. + // During bootup, the network may not have been up yet, or it's taking time for the + // connection to happen. + private int mTryAgainCounter; + + public NetworkTimeUpdateService(Context context) { + mContext = context; + mTime = NtpTrustedTime.getInstance(context); + mAlarmManager = mContext.getSystemService(AlarmManager.class); + mTimeDetector = mContext.getSystemService(TimeDetector.class); + mCM = mContext.getSystemService(ConnectivityManager.class); + + Intent pollIntent = new Intent(ACTION_POLL, null); + mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); + + mPollingIntervalMs = mContext.getResources().getInteger( + com.android.internal.R.integer.config_ntpPollingInterval); + mPollingIntervalShorterMs = mContext.getResources().getInteger( + com.android.internal.R.integer.config_ntpPollingIntervalShorter); + mTryAgainTimesMax = mContext.getResources().getInteger( + com.android.internal.R.integer.config_ntpRetry); + + mWakeLock = context.getSystemService(PowerManager.class).newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, TAG); + } /** Initialize the receivers and initiate the first NTP request */ - void systemRunning(); + public void systemRunning() { + registerForAlarms(); + + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + mHandler = new MyHandler(thread.getLooper()); + mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback(); + mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler); + + mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler, + EVENT_AUTO_TIME_ENABLED); + mAutoTimeSettingObserver.observe(); + } + + private void registerForAlarms() { + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + } + }, new IntentFilter(ACTION_POLL)); + } + + private void onPollNetworkTime(int event) { + // If we don't have any default network, don't bother. + if (mDefaultNetwork == null) return; + mWakeLock.acquire(); + try { + onPollNetworkTimeUnderWakeLock(event); + } finally { + mWakeLock.release(); + } + } + + private void onPollNetworkTimeUnderWakeLock(int event) { + // Force an NTP fix when outdated + NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult(); + if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) { + if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh"); + mTime.forceRefresh(); + cachedNtpResult = mTime.getCachedTimeResult(); + } + + if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) { + // Obtained fresh fix; schedule next normal update + resetAlarm(mPollingIntervalMs); + + // Suggest the time to the time detector. It may choose use it to set the system clock. + TimestampedValue<Long> timeSignal = new TimestampedValue<>( + cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis()); + NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal); + timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateService. event=" + event); + mTimeDetector.suggestNetworkTime(timeSuggestion); + } else { + // No fresh fix; schedule retry + mTryAgainCounter++; + if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) { + resetAlarm(mPollingIntervalShorterMs); + } else { + // Try much later + mTryAgainCounter = 0; + resetAlarm(mPollingIntervalMs); + } + } + } + + /** + * Cancel old alarm and starts a new one for the specified interval. + * + * @param interval when to trigger the alarm, starting from now. + */ + private void resetAlarm(long interval) { + mAlarmManager.cancel(mPendingPollIntent); + long now = SystemClock.elapsedRealtime(); + long next = now + interval; + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); + } + + /** Handler to do the network accesses on */ + private class MyHandler extends Handler { + + MyHandler(Looper l) { + super(l); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AUTO_TIME_ENABLED: + case EVENT_POLL_NETWORK_TIME: + case EVENT_NETWORK_CHANGED: + onPollNetworkTime(msg.what); + break; + } + } + } + + private class NetworkTimeUpdateCallback extends NetworkCallback { + @Override + public void onAvailable(Network network) { + Log.d(TAG, String.format("New default network %s; checking time.", network)); + mDefaultNetwork = network; + // Running on mHandler so invoke directly. + onPollNetworkTime(EVENT_NETWORK_CHANGED); + } + + @Override + public void onLost(Network network) { + if (network.equals(mDefaultNetwork)) mDefaultNetwork = null; + } + } + + /** + * Observer to watch for changes to the AUTO_TIME setting. It only triggers when the setting + * is enabled. + */ + private static class AutoTimeSettingObserver extends ContentObserver { + + private final Context mContext; + private final int mMsg; + private final Handler mHandler; + + AutoTimeSettingObserver(Context context, Handler handler, int msg) { + super(handler); + mContext = context; + mHandler = handler; + mMsg = msg; + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME), + false, this); + } + + @Override + public void onChange(boolean selfChange) { + if (isAutomaticTimeEnabled()) { + mHandler.obtainMessage(mMsg).sendToTarget(); + } + } + + /** + * Checks if the user prefers to automatically set the time. + */ + private boolean isAutomaticTimeEnabled() { + ContentResolver resolver = mContext.getContentResolver(); + return Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) != 0; + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + pw.print("PollingIntervalMs: "); + TimeUtils.formatDuration(mPollingIntervalMs, pw); + pw.print("\nPollingIntervalShorterMs: "); + TimeUtils.formatDuration(mPollingIntervalShorterMs, pw); + pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax); + pw.println("\nTryAgainCounter: " + mTryAgainCounter); + NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult(); + pw.println("NTP cache result: " + ntpResult); + if (ntpResult != null) { + pw.println("NTP result age: " + ntpResult.getAgeMillis()); + } + pw.println(); + } } diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java deleted file mode 100644 index 7894788cb0b6..000000000000 --- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.TimeDetector; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.ContentObserver; -import android.net.ConnectivityManager; -import android.net.ConnectivityManager.NetworkCallback; -import android.net.Network; -import android.os.Binder; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.PowerManager; -import android.os.SystemClock; -import android.os.TimestampedValue; -import android.provider.Settings; -import android.util.Log; -import android.util.NtpTrustedTime; -import android.util.TimeUtils; - -import com.android.internal.util.DumpUtils; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Monitors the network time. If looking up the network time fails for some reason, it tries a few - * times with a short interval and then resets to checking on longer intervals. - * - * <p>When available, the time is always suggested to the {@link - * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device - * system clock, depending on user settings and what other signals are available. - */ -public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeUpdateService { - - private static final String TAG = "NetworkTimeUpdateService"; - private static final boolean DBG = false; - - private static final int EVENT_AUTO_TIME_ENABLED = 1; - private static final int EVENT_POLL_NETWORK_TIME = 2; - private static final int EVENT_NETWORK_CHANGED = 3; - - private static final String ACTION_POLL = - "com.android.server.NetworkTimeUpdateService.action.POLL"; - - private static final int POLL_REQUEST = 0; - - private Network mDefaultNetwork = null; - - private final Context mContext; - private final NtpTrustedTime mTime; - private final AlarmManager mAlarmManager; - private final TimeDetector mTimeDetector; - private final ConnectivityManager mCM; - private final PendingIntent mPendingPollIntent; - private final PowerManager.WakeLock mWakeLock; - - // NTP lookup is done on this thread and handler - private Handler mHandler; - private AutoTimeSettingObserver mAutoTimeSettingObserver; - private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback; - - // Normal polling frequency - private final long mPollingIntervalMs; - // Try-again polling interval, in case the network request failed - private final long mPollingIntervalShorterMs; - // Number of times to try again - private final int mTryAgainTimesMax; - // Keeps track of how many quick attempts were made to fetch NTP time. - // During bootup, the network may not have been up yet, or it's taking time for the - // connection to happen. - private int mTryAgainCounter; - - public NetworkTimeUpdateServiceImpl(Context context) { - mContext = context; - mTime = NtpTrustedTime.getInstance(context); - mAlarmManager = mContext.getSystemService(AlarmManager.class); - mTimeDetector = mContext.getSystemService(TimeDetector.class); - mCM = mContext.getSystemService(ConnectivityManager.class); - - Intent pollIntent = new Intent(ACTION_POLL, null); - mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); - - mPollingIntervalMs = mContext.getResources().getInteger( - com.android.internal.R.integer.config_ntpPollingInterval); - mPollingIntervalShorterMs = mContext.getResources().getInteger( - com.android.internal.R.integer.config_ntpPollingIntervalShorter); - mTryAgainTimesMax = mContext.getResources().getInteger( - com.android.internal.R.integer.config_ntpRetry); - - mWakeLock = context.getSystemService(PowerManager.class).newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, TAG); - } - - @Override - public void systemRunning() { - registerForAlarms(); - - HandlerThread thread = new HandlerThread(TAG); - thread.start(); - mHandler = new MyHandler(thread.getLooper()); - mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback(); - mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler); - - mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler, - EVENT_AUTO_TIME_ENABLED); - mAutoTimeSettingObserver.observe(); - } - - private void registerForAlarms() { - mContext.registerReceiver( - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); - } - }, new IntentFilter(ACTION_POLL)); - } - - private void onPollNetworkTime(int event) { - // If we don't have any default network, don't bother. - if (mDefaultNetwork == null) return; - mWakeLock.acquire(); - try { - onPollNetworkTimeUnderWakeLock(event); - } finally { - mWakeLock.release(); - } - } - - private void onPollNetworkTimeUnderWakeLock(int event) { - // Force an NTP fix when outdated - NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult(); - if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) { - if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh"); - mTime.forceRefresh(); - cachedNtpResult = mTime.getCachedTimeResult(); - } - - if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) { - // Obtained fresh fix; schedule next normal update - resetAlarm(mPollingIntervalMs); - - // Suggest the time to the time detector. It may choose use it to set the system clock. - TimestampedValue<Long> timeSignal = new TimestampedValue<>( - cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis()); - NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal); - timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event); - mTimeDetector.suggestNetworkTime(timeSuggestion); - } else { - // No fresh fix; schedule retry - mTryAgainCounter++; - if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) { - resetAlarm(mPollingIntervalShorterMs); - } else { - // Try much later - mTryAgainCounter = 0; - resetAlarm(mPollingIntervalMs); - } - } - } - - /** - * Cancel old alarm and starts a new one for the specified interval. - * - * @param interval when to trigger the alarm, starting from now. - */ - private void resetAlarm(long interval) { - mAlarmManager.cancel(mPendingPollIntent); - long now = SystemClock.elapsedRealtime(); - long next = now + interval; - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); - } - - /** Handler to do the network accesses on */ - private class MyHandler extends Handler { - - public MyHandler(Looper l) { - super(l); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_AUTO_TIME_ENABLED: - case EVENT_POLL_NETWORK_TIME: - case EVENT_NETWORK_CHANGED: - onPollNetworkTime(msg.what); - break; - } - } - } - - private class NetworkTimeUpdateCallback extends NetworkCallback { - @Override - public void onAvailable(Network network) { - Log.d(TAG, String.format("New default network %s; checking time.", network)); - mDefaultNetwork = network; - // Running on mHandler so invoke directly. - onPollNetworkTime(EVENT_NETWORK_CHANGED); - } - - @Override - public void onLost(Network network) { - if (network.equals(mDefaultNetwork)) mDefaultNetwork = null; - } - } - - /** - * Observer to watch for changes to the AUTO_TIME setting. It only triggers when the setting - * is enabled. - */ - private static class AutoTimeSettingObserver extends ContentObserver { - - private final Context mContext; - private final int mMsg; - private final Handler mHandler; - - AutoTimeSettingObserver(Context context, Handler handler, int msg) { - super(handler); - mContext = context; - mHandler = handler; - mMsg = msg; - } - - void observe() { - ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME), - false, this); - } - - @Override - public void onChange(boolean selfChange) { - if (isAutomaticTimeEnabled()) { - mHandler.obtainMessage(mMsg).sendToTarget(); - } - } - - /** - * Checks if the user prefers to automatically set the time. - */ - private boolean isAutomaticTimeEnabled() { - ContentResolver resolver = mContext.getContentResolver(); - return Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) != 0; - } - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - pw.print("PollingIntervalMs: "); - TimeUtils.formatDuration(mPollingIntervalMs, pw); - pw.print("\nPollingIntervalShorterMs: "); - TimeUtils.formatDuration(mPollingIntervalShorterMs, pw); - pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax); - pw.println("\nTryAgainCounter: " + mTryAgainCounter); - NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult(); - pw.println("NTP cache result: " + ntpResult); - if (ntpResult != null) { - pw.println("NTP result age: " + ntpResult.getAgeMillis()); - } - pw.println(); - } -} diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index f5e342323831..32830aeacf06 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -37,6 +37,8 @@ import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED; import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED; import static android.os.storage.OnObbStateChangeListener.MOUNTED; import static android.os.storage.OnObbStateChangeListener.UNMOUNTED; +import static android.os.storage.StorageManager.PROP_FUSE; +import static android.os.storage.StorageManager.PROP_SETTINGS_FUSE; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; @@ -1617,7 +1619,10 @@ class StorageManagerService extends IStorageManager.Stub SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString( SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true))); - mIsFuseEnabled = SystemProperties.getBoolean(StorageManager.PROP_FUSE, false); + // If there is no value in the property yet (first boot after data wipe), this value may be + // incorrect until #updateFusePropFromSettings where we set the correct value and reboot if + // different + mIsFuseEnabled = SystemProperties.getBoolean(PROP_FUSE, false); mContext = context; mResolver = mContext.getContentResolver(); mCallbacks = new Callbacks(FgThread.get().getLooper()); @@ -1680,25 +1685,24 @@ class StorageManagerService extends IStorageManager.Stub * and updates PROP_FUSE (reboots if changed). */ private void updateFusePropFromSettings() { - String settingsFuseFlag = SystemProperties.get(StorageManager.PROP_SETTINGS_FUSE); - Slog.d(TAG, "The value of Settings Fuse Flag is " - + (settingsFuseFlag == null || settingsFuseFlag.isEmpty() - ? "null" : settingsFuseFlag)); - // Set default value of PROP_SETTINGS_FUSE and PROP_FUSE if it - // is unset (neither true nor false, this happens only on the first boot - // after wiping data partition). - if (settingsFuseFlag == null || settingsFuseFlag.isEmpty()) { - SystemProperties.set(StorageManager.PROP_SETTINGS_FUSE, "false"); - SystemProperties.set(StorageManager.PROP_FUSE, "false"); + boolean defaultFuseFlag = false; + boolean settingsFuseFlag = SystemProperties.getBoolean(PROP_SETTINGS_FUSE, defaultFuseFlag); + Slog.d(TAG, "FUSE flags. Settings: " + settingsFuseFlag + ". Default: " + defaultFuseFlag); + + if (TextUtils.isEmpty(SystemProperties.get(PROP_SETTINGS_FUSE))) { + // Set default value of PROP_SETTINGS_FUSE and PROP_FUSE if it + // is unset (neither true nor false). + // This happens only on the first boot after wiping data partition + SystemProperties.set(PROP_SETTINGS_FUSE, Boolean.toString(defaultFuseFlag)); + SystemProperties.set(PROP_FUSE, Boolean.toString(defaultFuseFlag)); return; } - if (!SystemProperties.get(StorageManager.PROP_FUSE).equals(settingsFuseFlag)) { - Slog.d(TAG, "Set persist.sys.fuse to " + settingsFuseFlag); - SystemProperties.set(StorageManager.PROP_FUSE, settingsFuseFlag); + if (mIsFuseEnabled != settingsFuseFlag) { + Slog.i(TAG, "Toggling persist.sys.fuse to " + settingsFuseFlag); + SystemProperties.set(PROP_FUSE, Boolean.toString(settingsFuseFlag)); // Perform hard reboot to kick policy into place - mContext.getSystemService(PowerManager.class).reboot("Reboot device for FUSE system" - + "property change to take effect"); + mContext.getSystemService(PowerManager.class).reboot("fuse_prop"); } } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 9ffe89c61a44..b994e6c58e64 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityTaskManager; +import android.app.AlarmManager; import android.app.IUiModeManager; import android.app.Notification; import android.app.NotificationManager; @@ -70,10 +71,19 @@ import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; import java.util.HashMap; import java.util.Map; import java.util.Set; +import static android.app.UiModeManager.MODE_NIGHT_AUTO; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; +import static android.app.UiModeManager.MODE_NIGHT_YES; +import static android.util.TimeUtils.isTimeBetween; + final class UiModeManagerService extends SystemService { private static final String TAG = UiModeManager.class.getSimpleName(); private static final boolean LOG = false; @@ -90,7 +100,12 @@ final class UiModeManagerService extends SystemService { // we use the override auto mode // for example: force night mode off in the night time while in auto mode private int mNightModeOverride = mNightMode; - protected static final String OVERRIDE_NIGHT_MODE = Secure.UI_NIGHT_MODE + "_override"; + private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0); + private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0); + private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME; + private LocalTime mCustomAutoNightModeEndMilliseconds = DEFAULT_CUSTOM_NIGHT_END_TIME; + + protected static final String OVERRIDE_NIGHT_MODE = Secure.UI_NIGHT_MODE_OVERRIDE; private Map<Integer, String> mCarModePackagePriority = new HashMap<>(); private boolean mCarModeEnabled = false; @@ -131,6 +146,8 @@ final class UiModeManagerService extends SystemService { private NotificationManager mNotificationManager; private StatusBarManager mStatusBarManager; private WindowManagerInternal mWindowManager; + private AlarmManager mAlarmManager; + private PowerManager mPowerManager; private PowerManager.WakeLock mWakeLock; @@ -141,14 +158,16 @@ final class UiModeManagerService extends SystemService { } @VisibleForTesting - protected UiModeManagerService(Context context, WindowManagerInternal wm, - PowerManager.WakeLock wl, TwilightManager tm, + protected UiModeManagerService(Context context, WindowManagerInternal wm, AlarmManager am, + PowerManager pm, PowerManager.WakeLock wl, TwilightManager tm, boolean setupWizardComplete) { super(context); mWindowManager = wm; mWakeLock = wl; mTwilightManager = tm; mSetupWizardComplete = setupWizardComplete; + mAlarmManager = am; + mPowerManager = pm; } private static Intent buildHomeIntent(String category) { @@ -237,6 +256,21 @@ final class UiModeManagerService extends SystemService { } }; + private final BroadcastReceiver mOnTimeChangedHandler = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + updateCustomTimeLocked(); + } + } + }; + + private final AlarmManager.OnAlarmListener mCustomTimeListener = () -> { + synchronized (mLock) { + updateCustomTimeLocked(); + } + }; + private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { @Override public void onVrStateChanged(boolean enabled) { @@ -270,8 +304,9 @@ final class UiModeManagerService extends SystemService { public void onChange(boolean selfChange, Uri uri) { int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, mNightMode, 0); - mode = mode == UiModeManager.MODE_NIGHT_AUTO - ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO; + if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) { + mode = MODE_NIGHT_YES; + } SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode)); } }; @@ -287,10 +322,11 @@ final class UiModeManagerService extends SystemService { public void onStart() { final Context context = getContext(); - final PowerManager powerManager = + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); + mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); mWindowManager = LocalServices.getService(WindowManagerInternal.class); + mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); // If setup isn't complete for this user listen for completion so we can unblock // being able to send a night mode configuration change event @@ -387,6 +423,16 @@ final class UiModeManagerService extends SystemService { Secure.USER_SETUP_COMPLETE, 0, UserHandle.getCallingUserId()) == 1; } + private void updateCustomTimeLocked() { + if (mNightMode != MODE_NIGHT_CUSTOM) return; + if (shouldApplyAutomaticChangesImmediately()) { + updateLocked(0, 0); + } else { + registerScreenOffEvent(); + } + scheduleNextCustomTimeListener(); + } + /** * Updates the night mode setting in Settings.Global and returns if the value was successfully * changed. @@ -404,9 +450,19 @@ final class UiModeManagerService extends SystemService { Secure.UI_NIGHT_MODE, defaultNightMode, userId); mNightModeOverride = Secure.getIntForUser(context.getContentResolver(), OVERRIDE_NIGHT_MODE, defaultNightMode, userId); + mCustomAutoNightModeStartMilliseconds = LocalTime.ofNanoOfDay( + Secure.getLongForUser(context.getContentResolver(), + Secure.DARK_THEME_CUSTOM_START_TIME, + DEFAULT_CUSTOM_NIGHT_START_TIME.toNanoOfDay() / 1000L, userId) * 1000); + mCustomAutoNightModeEndMilliseconds = LocalTime.ofNanoOfDay( + Secure.getLongForUser(context.getContentResolver(), + Secure.DARK_THEME_CUSTOM_END_TIME, + DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000); } else { mNightMode = defaultNightMode; mNightModeOverride = defaultNightMode; + mCustomAutoNightModeEndMilliseconds = DEFAULT_CUSTOM_NIGHT_END_TIME; + mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME; } return oldNightMode != mNightMode; @@ -419,6 +475,10 @@ final class UiModeManagerService extends SystemService { getContext().registerReceiver(mOnScreenOffHandler, intentFilter); } + private void cancelCustomAlarm() { + mAlarmManager.cancel(mCustomTimeListener); + } + private void unregisterScreenOffEvent() { mWaitForScreenOff = false; try { @@ -428,6 +488,21 @@ final class UiModeManagerService extends SystemService { } } + private void registerTimeChangeEvent() { + final IntentFilter intentFilter = + new IntentFilter(Intent.ACTION_TIME_CHANGED); + intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + getContext().registerReceiver(mOnTimeChangedHandler, intentFilter); + } + + private void unregisterTimeChangeEvent() { + try { + getContext().unregisterReceiver(mOnTimeChangedHandler); + } catch (IllegalArgumentException e) { + // we ignore this exception if the receiver is unregistered already. + } + } + private final IUiModeManager.Stub mService = new IUiModeManager.Stub() { @Override public void enableCarMode(@UiModeManager.EnableCarMode int flags, @@ -537,7 +612,8 @@ final class UiModeManagerService extends SystemService { switch (mode) { case UiModeManager.MODE_NIGHT_NO: case UiModeManager.MODE_NIGHT_YES: - case UiModeManager.MODE_NIGHT_AUTO: + case MODE_NIGHT_AUTO: + case MODE_NIGHT_CUSTOM: break; default: throw new IllegalArgumentException("Unknown mode: " + mode); @@ -548,8 +624,9 @@ final class UiModeManagerService extends SystemService { try { synchronized (mLock) { if (mNightMode != mode) { - if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) { unregisterScreenOffEvent(); + cancelCustomAlarm(); } mNightMode = mode; @@ -559,7 +636,9 @@ final class UiModeManagerService extends SystemService { persistNightMode(user); } // on screen off will update configuration instead - if (mNightMode != UiModeManager.MODE_NIGHT_AUTO || mCar) { + if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM) + || shouldApplyAutomaticChangesImmediately()) { + unregisterScreenOffEvent(); updateLocked(0, 0); } else { registerScreenOffEvent(); @@ -610,7 +689,7 @@ final class UiModeManagerService extends SystemService { final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { - if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) { unregisterScreenOffEvent(); mNightModeOverride = active ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO; @@ -630,8 +709,74 @@ final class UiModeManagerService extends SystemService { } } } + + @Override + public long getCustomNightModeStart() { + return mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000; + } + + @Override + public void setCustomNightModeStart(long time) { + if (isNightModeLocked() && getContext().checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + != PackageManager.PERMISSION_GRANTED) { + Slog.e(TAG, "Set custom time start, requires MODIFY_DAY_NIGHT_MODE permission"); + return; + } + final int user = UserHandle.getCallingUserId(); + final long ident = Binder.clearCallingIdentity(); + try { + LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000); + if (newTime == null) return; + mCustomAutoNightModeStartMilliseconds = newTime; + persistNightMode(user); + onCustomTimeUpdated(user); + } catch (DateTimeException e) { + unregisterScreenOffEvent(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public long getCustomNightModeEnd() { + return mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000; + } + + @Override + public void setCustomNightModeEnd(long time) { + if (isNightModeLocked() && getContext().checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + != PackageManager.PERMISSION_GRANTED) { + Slog.e(TAG, "Set custom time end, requires MODIFY_DAY_NIGHT_MODE permission"); + return; + } + final int user = UserHandle.getCallingUserId(); + final long ident = Binder.clearCallingIdentity(); + try { + LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000); + if (newTime == null) return; + mCustomAutoNightModeEndMilliseconds = newTime; + onCustomTimeUpdated(user); + } catch (DateTimeException e) { + unregisterScreenOffEvent(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } }; + private void onCustomTimeUpdated(int user) { + persistNightMode(user); + if (mNightMode != MODE_NIGHT_CUSTOM) return; + if (shouldApplyAutomaticChangesImmediately()) { + unregisterScreenOffEvent(); + updateLocked(0, 0); + } else { + registerScreenOffEvent(); + } + } + void dumpImpl(PrintWriter pw) { synchronized (mLock) { pw.println("Current UI Mode Service state:"); @@ -677,7 +822,6 @@ final class UiModeManagerService extends SystemService { mTwilightManager = getLocalService(TwilightManager.class); mSystemReady = true; mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; - updateComputedNightModeLocked(); registerVrStateListener(); updateLocked(0, 0); } @@ -838,6 +982,12 @@ final class UiModeManagerService extends SystemService { Secure.UI_NIGHT_MODE, mNightMode, user); Secure.putIntForUser(getContext().getContentResolver(), OVERRIDE_NIGHT_MODE, mNightModeOverride, user); + Secure.putLongForUser(getContext().getContentResolver(), + Secure.DARK_THEME_CUSTOM_START_TIME, + mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user); + Secure.putLongForUser(getContext().getContentResolver(), + Secure.DARK_THEME_CUSTOM_END_TIME, + mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000, user); } private void updateConfigurationLocked() { @@ -856,13 +1006,16 @@ final class UiModeManagerService extends SystemService { uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET; } - if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + if (mNightMode == MODE_NIGHT_AUTO) { + boolean activateNightMode = mComputedNightMode; if (mTwilightManager != null) { mTwilightManager.registerListener(mTwilightListener, mHandler); + final TwilightState lastState = mTwilightManager.getLastTwilightState(); + activateNightMode = lastState == null ? mComputedNightMode : lastState.isNight(); } - updateComputedNightModeLocked(); - uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES - : Configuration.UI_MODE_NIGHT_NO; + + updateComputedNightModeLocked(activateNightMode); + uiMode = getComputedUiModeConfiguration(uiMode); } else { if (mTwilightManager != null) { mTwilightManager.unregisterListener(mTwilightListener); @@ -870,6 +1023,16 @@ final class UiModeManagerService extends SystemService { uiMode |= mNightMode << 4; } + if (mNightMode == MODE_NIGHT_CUSTOM) { + registerTimeChangeEvent(); + final boolean activate = computeCustomNightMode(); + updateComputedNightModeLocked(activate); + scheduleNextCustomTimeListener(); + uiMode = getComputedUiModeConfiguration(uiMode); + } else { + unregisterTimeChangeEvent(); + } + // Override night mode in power save mode if not in car mode if (mPowerSave && !mCarModeEnabled) { uiMode &= ~Configuration.UI_MODE_NIGHT_NO; @@ -885,11 +1048,26 @@ final class UiModeManagerService extends SystemService { } mCurUiMode = uiMode; - if (!mHoldingConfiguration || !mWaitForScreenOff) { + if (!mHoldingConfiguration && !mWaitForScreenOff) { mConfiguration.uiMode = uiMode; } } + @UiModeManager.NightMode + private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) { + uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES + : Configuration.UI_MODE_NIGHT_NO; + uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO + : ~Configuration.UI_MODE_NIGHT_YES; + return uiMode; + } + + private boolean computeCustomNightMode() { + return isTimeBetween(LocalTime.now(), + mCustomAutoNightModeStartMilliseconds, + mCustomAutoNightModeEndMilliseconds); + } + private void applyConfigurationExternallyLocked() { if (mSetUiMode != mConfiguration.uiMode) { mSetUiMode = mConfiguration.uiMode; @@ -899,10 +1077,34 @@ final class UiModeManagerService extends SystemService { ActivityTaskManager.getService().updateConfiguration(mConfiguration); } catch (RemoteException e) { Slog.w(TAG, "Failure communicating with activity manager", e); + } catch (SecurityException e) { + Slog.e(TAG, "Activity does not have the ", e); } } } + private boolean shouldApplyAutomaticChangesImmediately() { + return mCar || !mPowerManager.isInteractive(); + } + + private void scheduleNextCustomTimeListener() { + cancelCustomAlarm(); + LocalDateTime now = LocalDateTime.now(); + final boolean active = computeCustomNightMode(); + final LocalDateTime next = active + ? getDateTimeAfter(mCustomAutoNightModeEndMilliseconds, now) + : getDateTimeAfter(mCustomAutoNightModeStartMilliseconds, now); + final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, mCustomTimeListener, null); + } + + private LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) { + final LocalDateTime ldt = LocalDateTime.of(compareTime.toLocalDate(), localTime); + + // Check if the local time has passed, if so return the same time tomorrow. + return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt; + } + void updateLocked(int enableFlags, int disableFlags) { String action = null; String oldAction = null; @@ -1133,26 +1335,21 @@ final class UiModeManagerService extends SystemService { } } - private void updateComputedNightModeLocked() { - if (mTwilightManager != null) { - TwilightState state = mTwilightManager.getLastTwilightState(); - if (state != null) { - mComputedNightMode = state.isNight(); - } - if (mNightModeOverride == UiModeManager.MODE_NIGHT_YES && !mComputedNightMode) { - mComputedNightMode = true; - return; - } - if (mNightModeOverride == UiModeManager.MODE_NIGHT_NO && mComputedNightMode) { - mComputedNightMode = false; - return; - } - - mNightModeOverride = mNightMode; - final int user = UserHandle.getCallingUserId(); - Secure.putIntForUser(getContext().getContentResolver(), - OVERRIDE_NIGHT_MODE, mNightModeOverride, user); + private void updateComputedNightModeLocked(boolean activate) { + mComputedNightMode = activate; + if (mNightModeOverride == UiModeManager.MODE_NIGHT_YES && !mComputedNightMode) { + mComputedNightMode = true; + return; + } + if (mNightModeOverride == UiModeManager.MODE_NIGHT_NO && mComputedNightMode) { + mComputedNightMode = false; + return; } + + mNightModeOverride = mNightMode; + final int user = UserHandle.getCallingUserId(); + Secure.putIntForUser(getContext().getContentResolver(), + OVERRIDE_NIGHT_MODE, mNightModeOverride, user); } private void registerVrStateListener() { @@ -1174,6 +1371,7 @@ final class UiModeManagerService extends SystemService { public static final String NIGHT_MODE_STR_YES = "yes"; public static final String NIGHT_MODE_STR_NO = "no"; public static final String NIGHT_MODE_STR_AUTO = "auto"; + public static final String NIGHT_MODE_STR_CUSTOM = "custom"; public static final String NIGHT_MODE_STR_UNKNOWN = "unknown"; private final IUiModeManager mInterface; @@ -1246,6 +1444,8 @@ final class UiModeManagerService extends SystemService { return NIGHT_MODE_STR_NO; case UiModeManager.MODE_NIGHT_AUTO: return NIGHT_MODE_STR_AUTO; + case MODE_NIGHT_CUSTOM: + return NIGHT_MODE_STR_CUSTOM; default: return NIGHT_MODE_STR_UNKNOWN; } @@ -1259,6 +1459,8 @@ final class UiModeManagerService extends SystemService { return UiModeManager.MODE_NIGHT_NO; case NIGHT_MODE_STR_AUTO: return UiModeManager.MODE_NIGHT_AUTO; + case NIGHT_MODE_STR_CUSTOM: + return UiModeManager.MODE_NIGHT_CUSTOM; default: return -1; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b0369752bdaf..d9fbc85fb6e1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -363,6 +363,7 @@ import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerService; +import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerService; import com.android.server.wm.WindowProcessController; @@ -1505,6 +1506,7 @@ public class ActivityManagerService extends IActivityManager.Stub @VisibleForTesting public WindowManagerService mWindowManager; + WindowManagerInternal mWmInternal; @VisibleForTesting public ActivityTaskManagerService mActivityTaskManager; @VisibleForTesting @@ -2085,6 +2087,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void setWindowManager(WindowManagerService wm) { synchronized (this) { mWindowManager = wm; + mWmInternal = LocalServices.getService(WindowManagerInternal.class); mActivityTaskManager.setWindowManager(wm); } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 0b115ea5ca5a..be14b3b03923 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -17,7 +17,9 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; @@ -33,6 +35,9 @@ import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; import static android.os.Process.SCHED_OTHER; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; @@ -1230,10 +1235,6 @@ public final class OomAdjuster { } } - if (app.hasLocationForegroundServices()) { - capability |= PROCESS_CAPABILITY_FOREGROUND_LOCATION; - } - if (adj > ProcessList.PERCEPTIBLE_APP_ADJ || procState > PROCESS_STATE_FOREGROUND_SERVICE) { if (app.hasForegroundServices()) { @@ -1397,6 +1398,7 @@ public final class OomAdjuster { } } + int capabilityFromFGS = 0; // capability from foreground service. for (int is = app.services.size() - 1; is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND @@ -1445,6 +1447,24 @@ public final class OomAdjuster { } } + if (s.isForeground) { + final int fgsType = s.foregroundServiceType; + capabilityFromFGS |= + (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) + != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; + if (s.appInfo.targetSdkVersion < Build.VERSION_CODES.R) { + capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + } else { + capabilityFromFGS |= + (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA) + != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA : 0; + capabilityFromFGS |= + (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE) + != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE : 0; + } + } + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections(); for (int conni = serviceConnections.size() - 1; conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ @@ -1905,6 +1925,10 @@ public final class OomAdjuster { } } + // apply capability from FGS. + if (app.hasForegroundServices()) { + capability |= capabilityFromFGS; + } // TOP process has all capabilities. if (procState <= PROCESS_STATE_TOP) { capability = PROCESS_CAPABILITY_ALL; diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index cbf058700909..0639db02469d 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -65,8 +65,6 @@ import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.Zygote; -import com.android.server.LocalServices; -import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; @@ -1804,9 +1802,6 @@ class ProcessRecord implements WindowProcessListener { /** current wait for debugger dialog */ private AppWaitingForDebuggerDialog mWaitDialog; - private final WindowManagerInternal mWmInternal = - LocalServices.getService(WindowManagerInternal.class); - boolean hasCrashDialogs() { return mCrashDialogs != null; } @@ -1932,7 +1927,9 @@ class ProcessRecord implements WindowProcessListener { } // If there is no foreground window display, fallback to last used display. if (displayContexts.isEmpty() || lastUsedOnly) { - displayContexts.add(mWmInternal.getTopFocusedDisplayUiContext()); + displayContexts.add(mService.mWmInternal != null + ? mService.mWmInternal.getTopFocusedDisplayUiContext() + : mService.mUiContext); } return displayContexts; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index e3b761e0773d..08136a3cb9e5 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -16,7 +16,9 @@ package com.android.server.appop; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; @@ -467,6 +469,12 @@ public class AppOpsService extends IAppOpsService.Stub { case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: return ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED; + case OP_CAMERA: + return ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) + ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED; + case OP_RECORD_AUDIO: + return ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) + ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED; default: return AppOpsManager.MODE_ALLOWED; } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d27b245d77ba..71ade6293216 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1346,13 +1346,21 @@ public final class DisplayManagerService extends SystemService { } private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) { - final IBinder token = getDisplayToken(displayId); - if (token == null) { - return null; + synchronized (mSyncRoot) { + final IBinder token = getDisplayToken(displayId); + if (token == null) { + return null; + } + final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId); + if (logicalDisplay == null) { + return null; + } + + final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); + return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), + false /* useIdentityTransform */, 0 /* rotation */); } - return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe( - token, new Rect(), 0 /* width */, 0 /* height */, - false /* useIdentityTransform */, 0 /* rotation */); } @VisibleForTesting diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index c518614a336f..bb954ab2b5e6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -169,6 +169,7 @@ import android.os.BestClock; import android.os.Binder; import android.os.Environment; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IDeviceIdleController; import android.os.INetworkManagementService; @@ -882,7 +883,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // Listen for subscriber changes mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener( - new OnSubscriptionsChangedListener(mHandler.getLooper()) { + new HandlerExecutor(mHandler), + new OnSubscriptionsChangedListener() { @Override public void onSubscriptionsChanged() { updateNetworksInternal(); diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index 2247e54ac5f7..e8cb1632bdc3 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -62,7 +62,7 @@ public class NotificationHistoryDatabase { private static final String TAG = "NotiHistoryDatabase"; private static final boolean DEBUG = NotificationManagerService.DBG; - private static final int HISTORY_RETENTION_DAYS = 2; + private static final int HISTORY_RETENTION_DAYS = 1; private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000; private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20; @@ -172,7 +172,7 @@ public class NotificationHistoryDatabase { public void addNotification(final HistoricalNotification notification) { synchronized (mLock) { - mBuffer.addNotificationToWrite(notification); + mBuffer.addNewNotificationToWrite(notification); // Each time we have new history to write to disk, schedule a write in [interval] ms if (mBuffer.getHistoryCount() == 1) { mFileWriteHandler.postDelayed(mWriteBufferRunnable, WRITE_BUFFER_INTERVAL_MS); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 385d84a93fbd..767a1c07d4e7 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -844,20 +844,22 @@ public class NotificationManagerService extends SystemService { private static final class ToastRecord { - final int pid; - final String pkg; - final ITransientNotification callback; - int duration; - int displayId; - Binder token; - - ToastRecord(int pid, String pkg, ITransientNotification callback, int duration, - Binder token, int displayId) { + public final int pid; + public final String pkg; + public final IBinder token; + public final ITransientNotification callback; + public int duration; + public int displayId; + public Binder windowToken; + + ToastRecord(int pid, String pkg, IBinder token, ITransientNotification callback, + int duration, Binder windowToken, int displayId) { this.pid = pid; this.pkg = pkg; + this.token = token; this.callback = callback; this.duration = duration; - this.token = token; + this.windowToken = windowToken; this.displayId = displayId; } @@ -876,8 +878,10 @@ public class NotificationManagerService extends SystemService { return "ToastRecord{" + Integer.toHexString(System.identityHashCode(this)) + " pkg=" + pkg + + " token=" + token + " callback=" + callback - + " duration=" + duration; + + " duration=" + duration + + "}"; } } @@ -2559,26 +2563,27 @@ public class NotificationManagerService extends SystemService { // ============================================================================ @Override - public void enqueueTextToast(String pkg, ITransientNotification callback, int duration, - int displayId) { - enqueueToast(pkg, callback, duration, displayId, false); + public void enqueueTextToast(String pkg, IBinder token, ITransientNotification callback, + int duration, int displayId) { + enqueueToast(pkg, token, callback, duration, displayId, false); } @Override - public void enqueueToast(String pkg, ITransientNotification callback, int duration, - int displayId) { - enqueueToast(pkg, callback, duration, displayId, true); + public void enqueueToast(String pkg, IBinder token, ITransientNotification callback, + int duration, int displayId) { + enqueueToast(pkg, token, callback, duration, displayId, true); } - private void enqueueToast(String pkg, ITransientNotification callback, int duration, - int displayId, boolean isCustomToast) { + private void enqueueToast(String pkg, IBinder token, ITransientNotification callback, + int duration, int displayId, boolean isCustomToast) { if (DBG) { Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration + " displayId=" + displayId); } - if (pkg == null || callback == null) { - Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " callback=" + callback); + if (pkg == null || callback == null || token == null) { + Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " callback=" + callback + " token=" + + token); return ; } @@ -2636,7 +2641,7 @@ public class NotificationManagerService extends SystemService { long callingId = Binder.clearCallingIdentity(); try { ToastRecord record; - int index = indexOfToastLocked(pkg, callback); + int index = indexOfToastLocked(pkg, token); // If it's already in the queue, we update it in place, we don't // move it to the end of the queue. if (index >= 0) { @@ -2661,10 +2666,10 @@ public class NotificationManagerService extends SystemService { } } - Binder token = new Binder(); - mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, displayId); - record = new ToastRecord(callingPid, pkg, callback, duration, token, - displayId); + Binder windowToken = new Binder(); + mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId); + record = new ToastRecord(callingPid, pkg, token, callback, duration, + windowToken, displayId); mToastQueue.add(record); index = mToastQueue.size() - 1; keepProcessAliveIfNeededLocked(callingPid); @@ -2683,23 +2688,23 @@ public class NotificationManagerService extends SystemService { } @Override - public void cancelToast(String pkg, ITransientNotification callback) { - Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback); + public void cancelToast(String pkg, IBinder token) { + Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token); - if (pkg == null || callback == null) { - Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback); + if (pkg == null || token == null) { + Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token); return ; } synchronized (mToastQueue) { long callingId = Binder.clearCallingIdentity(); try { - int index = indexOfToastLocked(pkg, callback); + int index = indexOfToastLocked(pkg, token); if (index >= 0) { cancelToastLocked(index); } else { Slog.w(TAG, "Toast already cancelled. pkg=" + pkg - + " callback=" + callback); + + " token=" + token); } } finally { Binder.restoreCallingIdentity(callingId); @@ -2708,17 +2713,17 @@ public class NotificationManagerService extends SystemService { } @Override - public void finishToken(String pkg, ITransientNotification callback) { + public void finishToken(String pkg, IBinder token) { synchronized (mToastQueue) { long callingId = Binder.clearCallingIdentity(); try { - int index = indexOfToastLocked(pkg, callback); + int index = indexOfToastLocked(pkg, token); if (index >= 0) { ToastRecord record = mToastQueue.get(index); - finishTokenLocked(record.token, record.displayId); + finishWindowTokenLocked(record.windowToken, record.displayId); } else { Slog.w(TAG, "Toast already killed. pkg=" + pkg - + " callback=" + callback); + + " token=" + token); } } finally { Binder.restoreCallingIdentity(callingId); @@ -6757,7 +6762,7 @@ public class NotificationManagerService extends SystemService { while (record != null) { if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); try { - record.callback.show(record.token); + record.callback.show(record.windowToken); scheduleDurationReachedLocked(record); return; } catch (RemoteException e) { @@ -6792,7 +6797,7 @@ public class NotificationManagerService extends SystemService { ToastRecord lastToast = mToastQueue.remove(index); - mWindowManagerInternal.removeWindowToken(lastToast.token, false /* removeWindows */, + mWindowManagerInternal.removeWindowToken(lastToast.windowToken, false /* removeWindows */, lastToast.displayId); // We passed 'false' for 'removeWindows' so that the client has time to stop // rendering (as hide above is a one-way message), otherwise we could crash @@ -6810,7 +6815,7 @@ public class NotificationManagerService extends SystemService { } } - void finishTokenLocked(IBinder t, int displayId) { + void finishWindowTokenLocked(IBinder t, int displayId) { mHandler.removeCallbacksAndMessages(t); // We pass 'true' for 'removeWindows' to let the WindowManager destroy any // remaining surfaces as either the client has called finishToken indicating @@ -6835,9 +6840,9 @@ public class NotificationManagerService extends SystemService { private void handleDurationReached(ToastRecord record) { - if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback); + if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " token=" + record.token); synchronized (mToastQueue) { - int index = indexOfToastLocked(record.pkg, record.callback); + int index = indexOfToastLocked(record.pkg, record.token); if (index >= 0) { cancelToastLocked(index); } @@ -6854,21 +6859,19 @@ public class NotificationManagerService extends SystemService { private void handleKillTokenTimeout(ToastRecord record) { - if (DBG) Slog.d(TAG, "Kill Token Timeout token=" + record.token); + if (DBG) Slog.d(TAG, "Kill Token Timeout token=" + record.windowToken); synchronized (mToastQueue) { - finishTokenLocked(record.token, record.displayId); + finishWindowTokenLocked(record.windowToken, record.displayId); } } @GuardedBy("mToastQueue") - int indexOfToastLocked(String pkg, ITransientNotification callback) - { - IBinder cbak = callback.asBinder(); + int indexOfToastLocked(String pkg, IBinder token) { ArrayList<ToastRecord> list = mToastQueue; int len = list.size(); for (int i=0; i<len; i++) { ToastRecord r = list.get(i); - if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) { + if (r.pkg.equals(pkg) && r.token == token) { return i; } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d0f91c206a2c..07aec4ad41d4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -124,6 +124,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ApplicationPackageManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IActivityManager; @@ -2721,6 +2722,7 @@ public class PackageManagerService extends IPackageManager.Stub t.traceBegin("get system config"); SystemConfig systemConfig = SystemConfig.getInstance(); mAvailableFeatures = systemConfig.getAvailableFeatures(); + ApplicationPackageManager.invalidateSysFeatureCache(); t.traceEnd(); mProtectedPackages = new ProtectedPackages(mContext); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 5adab378bb73..ad4f6ff2b003 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -81,6 +81,7 @@ import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; @@ -132,6 +133,7 @@ import java.util.concurrent.TimeUnit; class PackageManagerShellCommand extends ShellCommand { /** Path for streaming APK content */ private static final String STDIN_PATH = "-"; + private static final byte[] STDIN_PATH_BYTES = "-".getBytes(StandardCharsets.UTF_8); /** Path where ART profiles snapshots are dumped for the shell user */ private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/"; private static final int DEFAULT_WAIT_MS = 60 * 1000; @@ -472,6 +474,7 @@ class PackageManagerShellCommand extends ShellCommand { } } } + params.sessionParams.setSize(sessionSize); } /** @@ -1170,57 +1173,52 @@ class PackageManagerShellCommand extends ShellCommand { private int doRunInstall(final InstallParams params) throws RemoteException { final PrintWriter pw = getOutPrintWriter(); - final boolean streaming = params.sessionParams.dataLoaderParams != null; - ArrayList<String> inPaths = getRemainingArgs(); - if (inPaths.isEmpty()) { - inPaths.add(STDIN_PATH); - } + final boolean isStreaming = params.sessionParams.dataLoaderParams != null; + final boolean isApex = + (params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0; - final boolean hasSplits = inPaths.size() > 1; + ArrayList<String> args = getRemainingArgs(); - if (STDIN_PATH.equals(inPaths.get(0))) { - if (hasSplits) { - pw.println("Error: can't specify SPLIT(s) along with STDIN"); - return 1; - } - if (params.sessionParams.sizeBytes == -1) { - pw.println("Error: must either specify a package size or an APK file"); - return 1; - } + final boolean fromStdIn = args.isEmpty() || STDIN_PATH.equals(args.get(0)); + final boolean hasSplits = args.size() > 1; + + if (fromStdIn && params.sessionParams.sizeBytes == -1) { + pw.println("Error: must either specify a package size or an APK file"); + return 1; } - final boolean isApex = - (params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0; if (isApex && hasSplits) { pw.println("Error: can't specify SPLIT(s) for APEX"); return 1; } - if (!streaming) { - setParamsSize(params, inPaths); + if (!isStreaming) { + if (fromStdIn && hasSplits) { + pw.println("Error: can't specify SPLIT(s) along with STDIN"); + return 1; + } + + if (args.isEmpty()) { + args.add(STDIN_PATH); + } else { + setParamsSize(params, args); + } } final int sessionId = doCreateSession(params.sessionParams, params.installerPackageName, params.userId); boolean abandonSession = true; try { - for (String inPath : inPaths) { - if (streaming) { - String name = new File(inPath).getName(); - byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8); - if (doAddFile(sessionId, name, params.sessionParams.sizeBytes, metadata, - false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - } else { - String splitName = hasSplits ? new File(inPath).getName() - : "base." + (isApex ? "apex" : "apk"); - - if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName, - false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { - return 1; - } + if (isStreaming) { + if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex) + != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + } else { + if (doWriteSplits(sessionId, args, params.sessionParams.sizeBytes, isApex) + != PackageInstaller.STATUS_SUCCESS) { + return 1; } } if (doCommitSession(sessionId, false /*logSuccess*/) @@ -2519,7 +2517,7 @@ class PackageManagerShellCommand extends ShellCommand { } name = arg; - UserInfo info; + UserInfo info = null; IUserManager um = IUserManager.Stub.asInterface( ServiceManager.getService(Context.USER_SERVICE)); IAccountManager accm = IAccountManager.Stub.asInterface( @@ -2527,17 +2525,22 @@ class PackageManagerShellCommand extends ShellCommand { if (userType == null) { userType = UserInfo.getDefaultUserType(flags); } - if (UserManager.isUserTypeRestricted(userType)) { - // In non-split user mode, userId can only be SYSTEM - int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; - info = um.createRestrictedProfile(name, parentUserId); - accm.addSharedAccountsFromParentUser(parentUserId, userId, - (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); - } else if (userId < 0) { - info = preCreateOnly ? - um.preCreateUser(userType) : um.createUser(name, userType, flags); - } else { - info = um.createProfileForUser(name, userType, flags, userId, null); + try { + if (UserManager.isUserTypeRestricted(userType)) { + // In non-split user mode, userId can only be SYSTEM + int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; + info = um.createRestrictedProfileWithThrow(name, parentUserId); + accm.addSharedAccountsFromParentUser(parentUserId, userId, + (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); + } else if (userId < 0) { + info = preCreateOnly ? + um.preCreateUserWithThrow(userType) : + um.createUserWithThrow(name, userType, flags); + } else { + info = um.createProfileForUserWithThrow(name, userType, flags, userId, null); + } + } catch (ServiceSpecificException e) { + getErrPrintWriter().println("Error: " + e); } if (info != null) { @@ -2956,23 +2959,71 @@ class PackageManagerShellCommand extends ShellCommand { return sessionId; } - private int doAddFile(int sessionId, String name, long sizeBytes, byte[] metadata, - boolean logSuccess) throws RemoteException { + private int doAddFiles(int sessionId, ArrayList<String> args, long sessionSizeBytes, + boolean isApex) throws RemoteException { PackageInstaller.Session session = new PackageInstaller.Session( mInterface.getPackageInstaller().openSession(sessionId)); try { - session.addFile(name, sizeBytes, metadata); - - if (logSuccess) { - getOutPrintWriter().println("Success"); + // 1. Single file from stdin. + if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) { + String name = "base." + (isApex ? "apex" : "apk"); + session.addFile(name, sessionSizeBytes, STDIN_PATH_BYTES); + return 0; } + for (String arg : args) { + final int delimLocation = arg.indexOf(':'); + + // 2. File with specified size read from stdin. + if (delimLocation != -1) { + String name = arg.substring(0, delimLocation); + String sizeStr = arg.substring(delimLocation + 1); + long sizeBytes; + + if (TextUtils.isEmpty(name)) { + getErrPrintWriter().println("Empty file name in: " + arg); + return 1; + } + try { + sizeBytes = Long.parseUnsignedLong(sizeStr); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Unable to parse size from: " + arg); + return 1; + } + + session.addFile(name, sizeBytes, STDIN_PATH_BYTES); + continue; + } + + // 3. Local file. + final String inPath = arg; + + String name = new File(inPath).getName(); + byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8); + + session.addFile(name, -1, metadata); + } return 0; } finally { IoUtils.closeQuietly(session); } } + private int doWriteSplits(int sessionId, ArrayList<String> splitPaths, long sessionSizeBytes, + boolean isApex) throws RemoteException { + final boolean multipleSplits = splitPaths.size() > 1; + for (String splitPath : splitPaths) { + String splitName = multipleSplits ? new File(splitPath).getName() + : "base." + (isApex ? "apex" : "apk"); + + if (doWriteSplit(sessionId, splitPath, sessionSizeBytes, splitName, + false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + } + return 0; + } + private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName, boolean logSuccess) throws RemoteException { PackageInstaller.Session session = null; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 2a249d2c92ec..5511a54de2cd 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -67,6 +67,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SELinux; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; @@ -1538,12 +1539,14 @@ public class UserManagerService extends IUserManager.Stub { @Override public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) { - checkManageUsersPermission("update users"); - if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) { - Slog.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled."); - return; + try { + checkManageUsersPermission("update users"); + enforceUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId, + "Cannot set user icon"); + mLocalService.setUserIcon(userId, bitmap); + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); } - mLocalService.setUserIcon(userId, bitmap); } @@ -3037,34 +3040,53 @@ public class UserManagerService extends IUserManager.Stub { /** * Creates a profile user. Used for actual profiles, like - * {@link UserManager#USER_TYPE_PROFILE_MANAGED}, as well as for - * {@link UserManager#USER_TYPE_FULL_RESTRICTED}. + * {@link UserManager#USER_TYPE_PROFILE_MANAGED}, + * as well as for {@link UserManager#USER_TYPE_FULL_RESTRICTED}. */ @Override - public UserInfo createProfileForUser(String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) { + public UserInfo createProfileForUserWithThrow(String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) + throws ServiceSpecificException { checkManageOrCreateUsersPermission(flags); - return createUserInternal(name, userType, flags, userId, disallowedPackages); + try { + return createUserInternal(name, userType, flags, userId, disallowedPackages); + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); + } } - /** @see #createProfileForUser */ + /** + * @see #createProfileForUser + */ @Override - public UserInfo createProfileForUserEvenWhenDisallowed(String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) { + public UserInfo createProfileForUserEvenWhenDisallowedWithThrow(String name, + @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) + throws ServiceSpecificException { checkManageOrCreateUsersPermission(flags); - return createUserInternalUnchecked(name, userType, flags, userId, - /* preCreate= */ false, disallowedPackages); + try { + return createUserInternalUnchecked(name, userType, flags, userId, + /* preCreate= */ false, disallowedPackages); + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); + } } @Override - public UserInfo createUser(String name, @NonNull String userType, @UserInfoFlag int flags) { + public UserInfo createUserWithThrow(String name, @NonNull String userType, + @UserInfoFlag int flags) + throws ServiceSpecificException { checkManageOrCreateUsersPermission(flags); - return createUserInternal(name, userType, flags, UserHandle.USER_NULL, - /* disallowedPackages= */ null); + try { + return createUserInternal(name, userType, flags, UserHandle.USER_NULL, + /* disallowedPackages= */ null); + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); + } } @Override - public UserInfo preCreateUser(String userType) { + public UserInfo preCreateUserWithThrow(String userType) throws ServiceSpecificException { final UserTypeDetails userTypeDetails = mUserTypes.get(userType); final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0; @@ -3074,28 +3096,32 @@ public class UserManagerService extends IUserManager.Stub { "cannot pre-create user of type " + userType); Slog.i(LOG_TAG, "Pre-creating user of type " + userType); - return createUserInternalUnchecked(/* name= */ null, userType, flags, - /* parentId= */ UserHandle.USER_NULL, /* preCreate= */ true, - /* disallowedPackages= */ null); + try { + return createUserInternalUnchecked(/* name= */ null, userType, flags, + /* parentId= */ UserHandle.USER_NULL, /* preCreate= */ true, + /* disallowedPackages= */ null); + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); + } } private UserInfo createUserInternal(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, - @Nullable String[] disallowedPackages) { + @Nullable String[] disallowedPackages) + throws UserManager.CheckedUserOperationException { String restriction = (UserManager.isUserTypeManagedProfile(userType)) ? UserManager.DISALLOW_ADD_MANAGED_PROFILE : UserManager.DISALLOW_ADD_USER; - if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) { - Slog.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled."); - return null; - } + enforceUserRestriction(restriction, UserHandle.getCallingUserId(), + "Cannot add user"); return createUserInternalUnchecked(name, userType, flags, parentId, /* preCreate= */ false, disallowedPackages); } private UserInfo createUserInternalUnchecked(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, - boolean preCreate, @Nullable String[] disallowedPackages) { + boolean preCreate, @Nullable String[] disallowedPackages) + throws UserManager.CheckedUserOperationException { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("createUser-" + flags); try { @@ -3109,7 +3135,7 @@ public class UserManagerService extends IUserManager.Stub { private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages, - @NonNull TimingsTraceAndSlog t) { + @NonNull TimingsTraceAndSlog t) throws UserManager.CheckedUserOperationException { final UserTypeDetails userTypeDetails = mUserTypes.get(userType); if (userTypeDetails == null) { Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType); @@ -3144,8 +3170,8 @@ public class UserManagerService extends IUserManager.Stub { DeviceStorageMonitorInternal dsm = LocalServices .getService(DeviceStorageMonitorInternal.class); if (dsm.isMemoryLow()) { - Slog.w(LOG_TAG, "Cannot add user. Not enough space on disk."); - return null; + throwCheckedUserOperationException("Cannot add user. Not enough space on disk.", + UserManager.USER_OPERATION_ERROR_LOW_STORAGE); } final boolean isProfile = userTypeDetails.isProfile(); @@ -3164,41 +3190,50 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mUsersLock) { parent = getUserDataLU(parentId); } - if (parent == null) return null; + if (parent == null) { + throwCheckedUserOperationException( + "Cannot find user data for parent user " + parentId, + UserManager.USER_OPERATION_ERROR_UNKNOWN); + } } if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) { - Slog.e(LOG_TAG, "Cannot add more users of type " + userType - + ". Maximum number of that type already exists."); - return null; + throwCheckedUserOperationException("Cannot add more users of type " + userType + + ". Maximum number of that type already exists.", + UserManager.USER_OPERATION_ERROR_MAX_USERS); } // TODO(b/142482943): Perhaps let the following code apply to restricted users too. if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) { - Slog.e(LOG_TAG, "Cannot add more profiles of type " + userType - + " for user " + parentId); - return null; + throwCheckedUserOperationException( + "Cannot add more profiles of type " + userType + + " for user " + parentId, + UserManager.USER_OPERATION_ERROR_MAX_USERS); } if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) { // If we're not adding a guest/demo user or a profile and the 'user limit' has // been reached, cannot add a user. - Slog.e(LOG_TAG, "Cannot add user. Maximum user limit is reached."); - return null; + throwCheckedUserOperationException( + "Cannot add user. Maximum user limit is reached.", + UserManager.USER_OPERATION_ERROR_MAX_USERS); } // In legacy mode, restricted profile's parent can only be the owner user if (isRestricted && !UserManager.isSplitSystemUser() && (parentId != UserHandle.USER_SYSTEM)) { - Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner"); - return null; + throwCheckedUserOperationException( + "Cannot add restricted profile - parent user must be owner", + UserManager.USER_OPERATION_ERROR_UNKNOWN); } if (isRestricted && UserManager.isSplitSystemUser()) { if (parent == null) { - Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be " - + "specified"); - return null; + throwCheckedUserOperationException( + "Cannot add restricted profile - parent user must be specified", + UserManager.USER_OPERATION_ERROR_UNKNOWN); } if (!parent.info.canHaveProfile()) { - Slog.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be " - + "created for the specified parent user id " + parentId); - return null; + throwCheckedUserOperationException( + "Cannot add restricted profile - profiles cannot be created for " + + "the specified parent user id " + + parentId, + UserManager.USER_OPERATION_ERROR_UNKNOWN); } } @@ -3464,9 +3499,9 @@ public class UserManagerService extends IUserManager.Stub { * @hide */ @Override - public UserInfo createRestrictedProfile(String name, int parentUserId) { + public UserInfo createRestrictedProfileWithThrow(String name, int parentUserId) { checkManageOrCreateUsersPermission("setupRestrictedProfile"); - final UserInfo user = createProfileForUser( + final UserInfo user = createProfileForUserWithThrow( name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null); if (user == null) { return null; @@ -4715,7 +4750,8 @@ public class UserManagerService extends IUserManager.Stub { @Override public UserInfo createUserEvenWhenDisallowed(String name, @NonNull String userType, - @UserInfoFlag int flags, String[] disallowedPackages) { + @UserInfoFlag int flags, String[] disallowedPackages) + throws UserManager.CheckedUserOperationException { return createUserInternalUnchecked(name, userType, flags, UserHandle.USER_NULL, /* preCreated= */ false, disallowedPackages); } @@ -4875,6 +4911,38 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Check if user has restrictions + * @param restriction restrictions to check + * @param userId id of the user + * + * @throws {@link android.os.UserManager.CheckedUserOperationException} if user has any of the + * specified restrictions + */ + private void enforceUserRestriction(String restriction, @UserIdInt int userId, String message) + throws UserManager.CheckedUserOperationException { + if (hasUserRestriction(restriction, userId)) { + String errorMessage = (message != null ? (message + ": ") : "") + + restriction + " is enabled."; + Slog.w(LOG_TAG, errorMessage); + throw new UserManager.CheckedUserOperationException(errorMessage, + UserManager.USER_OPERATION_ERROR_UNKNOWN); + } + } + + /** + * Throws CheckedUserOperationException and shows error log + * @param message message for exception and logging + * @param userOperationResult result/error code + * @throws UserManager.CheckedUserOperationException + */ + private void throwCheckedUserOperationException(@NonNull String message, + @UserManager.UserOperationResult int userOperationResult) + throws UserManager.CheckedUserOperationException { + Slog.e(LOG_TAG, message); + throw new UserManager.CheckedUserOperationException(message, userOperationResult); + } + /* Remove all the users except of the system one. */ private void removeNonSystemUsers() { ArrayList<UserInfo> usersToRemove = new ArrayList<>(); diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 5283cc409d38..3dee853240a6 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -53,6 +54,7 @@ import android.view.textclassifier.TextSelection; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils; +import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -74,6 +76,19 @@ public final class TextClassificationManagerService extends ITextClassifierServi private static final String LOG_TAG = "TextClassificationManagerService"; + private static final ITextClassifierCallback NO_OP_CALLBACK = new ITextClassifierCallback() { + @Override + public void onSuccess(Bundle result) {} + + @Override + public void onFailure() {} + + @Override + public IBinder asBinder() { + return null; + } + }; + public static final class Lifecycle extends SystemService { private final TextClassificationManagerService mManagerService; @@ -158,28 +173,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextSelection.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); - Objects.requireNonNull(callback); - final int userId = request.getUserId(); - validateInput(mContext, request.getCallingPackageName(), userId); - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (!userState.bindLocked()) { - Slog.d(LOG_TAG, "Unable to bind TextClassifierService at suggestSelection."); - callback.onFailure(); - } else if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "suggestSelection")) { - return; - } - userState.mService.onSuggestSelection(sessionId, request, callback); - } else { - userState.mPendingRequests.add(new PendingRequest("suggestSelection", - () -> userState.mService.onSuggestSelection(sessionId, request, callback), - callback::onFailure, callback.asBinder(), this, userState, - Binder.getCallingUid())); - } - } + handleRequest( + request.getUserId(), + request.getCallingPackageName(), + /* attemptToBind= */ true, + service -> service.onSuggestSelection(sessionId, request, callback), + "onSuggestSelection", + callback); } @Override @@ -188,27 +189,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextClassification.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); - Objects.requireNonNull(callback); - final int userId = request.getUserId(); - validateInput(mContext, request.getCallingPackageName(), userId); - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (!userState.bindLocked()) { - Slog.d(LOG_TAG, "Unable to bind TextClassifierService at classifyText."); - callback.onFailure(); - } else if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), "classifyText")) { - return; - } - userState.mService.onClassifyText(sessionId, request, callback); - } else { - userState.mPendingRequests.add(new PendingRequest("classifyText", - () -> userState.mService.onClassifyText(sessionId, request, callback), - callback::onFailure, callback.asBinder(), this, userState, - Binder.getCallingUid())); - } - } + handleRequest( + request.getUserId(), + request.getCallingPackageName(), + /* attemptToBind= */ true, + service -> service.onClassifyText(sessionId, request, callback), + "onClassifyText", + callback); } @Override @@ -217,28 +205,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextLinks.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); - Objects.requireNonNull(callback); - final int userId = request.getUserId(); - validateInput(mContext, request.getCallingPackageName(), userId); - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (!userState.bindLocked()) { - Slog.d(LOG_TAG, "Unable to bind TextClassifierService at generateLinks."); - callback.onFailure(); - } else if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "generateLinks")) { - return; - } - userState.mService.onGenerateLinks(sessionId, request, callback); - } else { - userState.mPendingRequests.add(new PendingRequest("generateLinks", - () -> userState.mService.onGenerateLinks(sessionId, request, callback), - callback::onFailure, callback.asBinder(), this, userState, - Binder.getCallingUid())); - } - } + handleRequest( + request.getUserId(), + request.getCallingPackageName(), + /* attemptToBind= */ true, + service -> service.onGenerateLinks(sessionId, request, callback), + "onGenerateLinks", + callback); } @Override @@ -246,53 +220,34 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Nullable TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException { Objects.requireNonNull(event); - final int userId = event.getUserId(); - validateInput(mContext, event.getPackageName(), userId); - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "selectionEvent")) { - return; - } - userState.mService.onSelectionEvent(sessionId, event); - } else { - userState.mPendingRequests.add(new PendingRequest("selectionEvent", - () -> userState.mService.onSelectionEvent(sessionId, event), - null /* onServiceFailure */, null /* binder */, this, userState, - Binder.getCallingUid())); - } - } + handleRequest( + event.getUserId(), + event.getPackageName(), + /* attemptToBind= */ false, + service -> service.onSelectionEvent(sessionId, event), + "onSelectionEvent", + NO_OP_CALLBACK); } @Override public void onTextClassifierEvent( @Nullable TextClassificationSessionId sessionId, TextClassifierEvent event) throws RemoteException { Objects.requireNonNull(event); + final String packageName = event.getEventContext() == null ? null : event.getEventContext().getPackageName(); final int userId = event.getEventContext() == null ? UserHandle.getCallingUserId() : event.getEventContext().getUserId(); - validateInput(mContext, packageName, userId); - - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "textClassifierEvent")) { - return; - } - userState.mService.onTextClassifierEvent(sessionId, event); - } else { - userState.mPendingRequests.add(new PendingRequest("textClassifierEvent", - () -> userState.mService.onTextClassifierEvent(sessionId, event), - null /* onServiceFailure */, null /* binder */, this, userState, - Binder.getCallingUid())); - } - } + handleRequest( + userId, + packageName, + /* attemptToBind= */ false, + service -> service.onTextClassifierEvent(sessionId, event), + "onTextClassifierEvent", + NO_OP_CALLBACK); } @Override @@ -301,28 +256,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi TextLanguage.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); - Objects.requireNonNull(callback); - final int userId = request.getUserId(); - validateInput(mContext, request.getCallingPackageName(), userId); - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (!userState.bindLocked()) { - Slog.d(LOG_TAG, "Unable to bind TextClassifierService at detectLanguage."); - callback.onFailure(); - } else if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "detectLanguage")) { - return; - } - userState.mService.onDetectLanguage(sessionId, request, callback); - } else { - userState.mPendingRequests.add(new PendingRequest("detectLanguage", - () -> userState.mService.onDetectLanguage(sessionId, request, callback), - callback::onFailure, callback.asBinder(), this, userState, - Binder.getCallingUid())); - } - } + handleRequest( + request.getUserId(), + request.getCallingPackageName(), + /* attemptToBind= */ true, + service -> service.onDetectLanguage(sessionId, request, callback), + "onDetectLanguage", + callback); } @Override @@ -331,30 +272,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi ConversationActions.Request request, ITextClassifierCallback callback) throws RemoteException { Objects.requireNonNull(request); - Objects.requireNonNull(callback); - final int userId = request.getUserId(); - validateInput(mContext, request.getCallingPackageName(), userId); - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (!userState.bindLocked()) { - Slog.d(LOG_TAG, - "Unable to bind TextClassifierService at suggestConversationActions."); - callback.onFailure(); - } else if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "suggestConversationActions")) { - return; - } - userState.mService.onSuggestConversationActions(sessionId, request, callback); - } else { - userState.mPendingRequests.add(new PendingRequest("suggestConversationActions", - () -> userState.mService.onSuggestConversationActions(sessionId, request, - callback), - callback::onFailure, callback.asBinder(), this, userState, - Binder.getCallingUid())); - } - } + handleRequest( + request.getUserId(), + request.getCallingPackageName(), + /* attemptToBind= */ true, + service -> service.onSuggestConversationActions(sessionId, request, callback), + "onSuggestConversationActions", + callback); } @Override @@ -363,30 +288,18 @@ public final class TextClassificationManagerService extends ITextClassifierServi throws RemoteException { Objects.requireNonNull(sessionId); Objects.requireNonNull(classificationContext); - final int userId = classificationContext.getUserId(); - validateInput(mContext, classificationContext.getPackageName(), userId); - synchronized (mLock) { - UserState userState = getUserStateLocked(userId); - if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "createTextClassificationSession")) { - return; - } - userState.mService.onCreateTextClassificationSession( - classificationContext, sessionId); - mSessionUserIds.put(sessionId, userId); - } else { - userState.mPendingRequests.add(new PendingRequest("createTextClassificationSession", - () -> { - userState.mService.onCreateTextClassificationSession( - classificationContext, sessionId); - mSessionUserIds.put(sessionId, userId); - }, - null /* onServiceFailure */, null /* binder */, this, userState, - Binder.getCallingUid())); - } - } + final int userId = classificationContext.getUserId(); + handleRequest( + userId, + classificationContext.getPackageName(), + /* attemptToBind= */ false, + service -> { + service.onCreateTextClassificationSession(classificationContext, sessionId); + mSessionUserIds.put(sessionId, userId); + }, + "onCreateTextClassificationSession", + NO_OP_CALLBACK); } @Override @@ -398,27 +311,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi final int userId = mSessionUserIds.containsKey(sessionId) ? mSessionUserIds.get(sessionId) : UserHandle.getCallingUserId(); - validateInput(mContext, null /* packageName */, userId); - - UserState userState = getUserStateLocked(userId); - if (userState.isBoundLocked()) { - if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), - "destroyTextClassificationSession")) { - return; - } - userState.mService.onDestroyTextClassificationSession(sessionId); - mSessionUserIds.remove(sessionId); - } else { - userState.mPendingRequests.add( - new PendingRequest("destroyTextClassificationSession", - () -> { - userState.mService.onDestroyTextClassificationSession( - sessionId); - mSessionUserIds.remove(sessionId); - }, - null /* onServiceFailure */, null /* binder */, this, userState, - Binder.getCallingUid())); - } + handleRequest( + userId, + /* callingPackageName= */ null, + /* attemptToBind= */ false, + service -> { + service.onDestroyTextClassificationSession(sessionId); + mSessionUserIds.remove(sessionId); + }, + "onDestroyTextClassificationSession", + NO_OP_CALLBACK); } } @@ -466,6 +368,42 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + private void handleRequest( + @UserIdInt int userId, + @Nullable String callingPackageName, + boolean attemptToBind, + @NonNull ThrowingConsumer<ITextClassifierService> textClassifierServiceConsumer, + @NonNull String methodName, + @NonNull ITextClassifierCallback callback) + throws RemoteException { + Objects.requireNonNull(textClassifierServiceConsumer); + Objects.requireNonNull(methodName); + Objects.requireNonNull(callback); + + validateInput(mContext, callingPackageName, userId); + synchronized (mLock) { + UserState userState = getUserStateLocked(userId); + if (attemptToBind && !userState.bindLocked()) { + Slog.d(LOG_TAG, "Unable to bind TextClassifierService at " + methodName); + callback.onFailure(); + } else if (userState.isBoundLocked()) { + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), methodName)) { + return; + } + textClassifierServiceConsumer.accept(userState.mService); + } else { + userState.mPendingRequests.add( + new PendingRequest( + methodName, + () -> textClassifierServiceConsumer.accept(userState.mService), + callback::onFailure, callback.asBinder(), + this, + userState, + Binder.getCallingUid())); + } + } + } + private void unbindServiceIfNecessary() { final ComponentName serviceComponentName = TextClassifierService.getServiceComponentName(mContext); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 8bc8ff4fb492..0e13e6c8772a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -745,7 +745,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A synchronized (mAtmService.mGlobalLock) { Slog.w(TAG, "Activity stop timeout for " + ActivityRecord.this); if (isInHistory()) { - activityStopped(null /*icicle*/, null /*persistentState*/, null /*description*/); + activityStopped( + null /*icicle*/, null /*persistentState*/, null /*description*/); } } } @@ -1286,17 +1287,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateColorTransform(); - final ActivityStack oldStack = (oldTask != null) ? oldTask.getStack() : null; - final ActivityStack newStack = (newTask != null) ? newTask.getStack() : null; - // Inform old stack (if present) of activity removal and new stack (if set) of activity - // addition. - if (oldStack != newStack) { - if (oldStack != null) { - oldStack.onActivityRemovedFromStack(this); - } - if (newStack != null) { - newStack.onActivityAddedToStack(this); - } + if (oldTask != null) { + oldTask.cleanUpActivityReferences(this); + } + if (newTask != null && isState(RESUMED)) { + newTask.setResumedActivity(this, "onParentChanged"); } } @@ -2904,8 +2899,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Note: Call before {@link #removeFromHistory(String)}. */ void cleanUp(boolean cleanServices, boolean setState) { - final ActivityStack stack = getActivityStack(); - stack.onActivityRemovedFromStack(this); + task.cleanUpActivityReferences(this); deferRelaunchUntilPaused = false; frozenBeforeDestroy = false; @@ -5833,7 +5827,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean isWaitingForTransitionStart() { final DisplayContent dc = getDisplayContent(); - return dc.mAppTransition.isTransitionSet() + return dc != null && dc.mAppTransition.isTransitionSet() && (dc.mOpeningApps.contains(this) || dc.mClosingApps.contains(this) || dc.mChangingApps.contains(this)); diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 9e32ea085135..60e0f51ef0a0 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -80,7 +80,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; @@ -119,7 +118,6 @@ import static com.android.server.wm.StackProto.MINIMIZE_AMOUNT; import static com.android.server.wm.StackProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static java.lang.Integer.MAX_VALUE; @@ -153,6 +151,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.util.DisplayMetrics; import android.util.Log; @@ -162,7 +161,6 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.ITaskOrganizer; -import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import com.android.internal.annotations.GuardedBy; @@ -190,7 +188,7 @@ import java.util.function.Consumer; /** * State and management of a single stack of activities. */ -class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAnimationTarget { +class ActivityStack extends Task implements BoundsAnimationTarget { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_ATM; static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_APP = TAG + POSTFIX_APP; @@ -251,33 +249,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn RESTARTING_PROCESS } - final ActivityTaskManagerService mAtmService; - - /** - * When we are in the process of pausing an activity, before starting the - * next one, this variable holds the activity that is currently being paused. - */ - ActivityRecord mPausingActivity = null; - - /** - * This is the last activity that we put into the paused state. This is - * used to determine if we need to do an activity transition while sleeping, - * when we normally hold the top activity paused. - */ - ActivityRecord mLastPausedActivity = null; - - /** - * Activities that specify No History must be removed once the user navigates away from them. - * If the device goes to sleep with such an activity in the paused state then we save it here - * and finish it later if another activity replaces it on wakeup. - */ - ActivityRecord mLastNoHistoryActivity = null; - - /** - * Current activity that is resumed, or null if there is none. - */ - ActivityRecord mResumedActivity = null; - // The topmost Activity passed to convertToTranslucent(). When non-null it means we are // waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they // are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the @@ -294,11 +265,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn boolean mConfigWillChange; /** - * When set, will force the stack to report as invisible. - */ - boolean mForceHidden = false; - - /** * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */ boolean mInResumeTopActivity = false; @@ -311,18 +277,12 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn int mCurrentUser; - /** The attached Display's unique identifier, or -1 if detached */ - private int mDisplayId; - // Id of the previous display the stack was on. - int mPrevDisplayId = INVALID_DISPLAY; - /** Unique identifier */ final int mStackId; /** For comparison with DisplayContent bounds. */ private Rect mTmpRect = new Rect(); private Rect mTmpRect2 = new Rect(); - private Rect mTmpRect3 = new Rect(); /** For Pinned stack controlling. */ private Rect mTmpToBounds = new Rect(); @@ -336,9 +296,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn */ private final Rect mFullyAdjustedImeBounds = new Rect(); - /** ActivityRecords that are exiting, but still on screen for animations. */ - final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>(); - /** Detach this stack from its display when animation completes. */ // TODO: maybe tie this to WindowContainer#removeChild some how... private boolean mDeferRemoval; @@ -367,8 +324,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn Rect mPreAnimationBounds = new Rect(); - private Dimmer mDimmer = new Dimmer(this); - /** * For {@link #prepareSurfaces}. */ @@ -384,10 +339,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn /** List for processing through a set of activities */ private final ArrayList<ActivityRecord> mTmpActivities = new ArrayList<>(); - /** Run all ActivityStacks through this */ - protected final ActivityStackSupervisor mStackSupervisor; - protected final RootWindowContainer mRootWindowContainer; - private boolean mTopActivityOccludesKeyguard; private ActivityRecord mTopDismissingKeyguardActivity; @@ -638,52 +589,68 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } } - ActivityStack(DisplayContent display, int stackId, ActivityStackSupervisor supervisor, - int activityType) { - super(supervisor.mService.mWindowManager); - mStackId = stackId; - mDockedStackMinimizeThickness = - supervisor.mService.mWindowManager.mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_minimize_thickness); - EventLogTags.writeWmStackCreated(stackId); - mStackSupervisor = supervisor; - mAtmService = supervisor.mService; - mRootWindowContainer = mAtmService.mRootWindowContainer; - mHandler = new ActivityStackHandler(supervisor.mLooper); - mRemoteToken = new RemoteToken(this); - mCurrentUser = mAtmService.mAmInternal.getCurrentUserId(); - // Set display id before setting activity and window type to make sure it won't affect - // stacks on a wrong display. - mDisplayId = display.mDisplayId; + ActivityStack(DisplayContent display, int id, ActivityStackSupervisor supervisor, + int activityType, ActivityInfo info, Intent intent) { + this(supervisor.mService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, + null /*taskDescription*/, null /*stack*/); + setActivityType(activityType); } - /** - * This should be called when an activity in a child task changes state. This should only - * be called from - * {@link Task#onActivityStateChanged(ActivityRecord, ActivityState, String)}. - * @param record The {@link ActivityRecord} whose state has changed. - * @param state The new state. - * @param reason The reason for the change. - */ - void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) { - if (record == mResumedActivity && state != RESUMED) { - setResumedActivity(null, reason + " - onActivityStateChanged"); - } - - if (state == RESUMED) { - if (DEBUG_STACK) Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" - + reason); - setResumedActivity(record, reason + " - onActivityStateChanged"); - if (record == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(record, reason); - } - mStackSupervisor.mRecentTasks.add(record.getTask()); - } + ActivityStack(ActivityTaskManagerService atmService, int id, ActivityInfo info, Intent _intent, + IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, + ActivityManager.TaskDescription _taskDescription, ActivityStack stack) { + this(atmService, id, _intent, null /*_affinityIntent*/, null /*_affinity*/, + null /*_rootAffinity*/, null /*_realActivity*/, null /*_origActivity*/, + false /*_rootWasReset*/, false /*_autoRemoveRecents*/, false /*_askedCompatMode*/, + UserHandle.getUserId(info.applicationInfo.uid), 0 /*_effectiveUid*/, + null /*_lastDescription*/, System.currentTimeMillis(), + true /*neverRelinquishIdentity*/, + _taskDescription != null ? _taskDescription : new ActivityManager.TaskDescription(), + id, INVALID_TASK_ID, INVALID_TASK_ID, 0 /*taskAffiliationColor*/, + info.applicationInfo.uid, info.packageName, info.resizeMode, + info.supportsPictureInPicture(), false /*_realActivitySuspended*/, + false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, info, + _voiceSession, _voiceInteractor, stack); + } + + ActivityStack(ActivityTaskManagerService atmService, int id, Intent _intent, + Intent _affinityIntent, String _affinity, String _rootAffinity, + ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, + boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid, + String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity, + ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation, + int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, + String callingPackage, int resizeMode, boolean supportsPictureInPicture, + boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, + ActivityInfo info, IVoiceInteractionSession _voiceSession, + IVoiceInteractor _voiceInteractor, ActivityStack stack) { + super(atmService, id, _intent, _affinityIntent, _affinity, _rootAffinity, + _realActivity, _origActivity, _rootWasReset, _autoRemoveRecents, _askedCompatMode, + _userId, _effectiveUid, _lastDescription, lastTimeMoved, neverRelinquishIdentity, + _lastTaskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, + callingUid, callingPackage, resizeMode, supportsPictureInPicture, + _realActivitySuspended, userSetupComplete, minWidth, minHeight, info, _voiceSession, + _voiceInteractor, stack); + + mStackId = mTaskId; + mDockedStackMinimizeThickness = mWmService.mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_minimize_thickness); + EventLogTags.writeWmStackCreated(id); + mHandler = new ActivityStackHandler(mStackSupervisor.mLooper); + mCurrentUser = mAtmService.mAmInternal.getCurrentUserId(); } @Override public void onConfigurationChanged(Configuration newParentConfig) { + // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are + // particularly for ActivityStack, like preventing bounds changes when inheriting certain + // windowing mode. + if (!isRootTask()) { + super.onConfigurationChanged(newParentConfig); + return; + } + final int prevWindowingMode = getWindowingMode(); final boolean prevIsAlwaysOnTop = isAlwaysOnTop(); final int prevRotation = getWindowConfiguration().getRotation(); @@ -801,10 +768,16 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn @Override public void setWindowingMode(int windowingMode) { + // Calling Task#setWindowingMode() for leaf task since this is the a specialization of + // {@link #setWindowingMode(int)} for ActivityStack. + if (!isRootTask()) { + super.setWindowingMode(windowingMode); + return; + } + setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, false /* creating */); - windowingMode = getWindowingMode(); /* * Different windowing modes may be managed by different task organizers. If @@ -893,7 +866,8 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn // Looks like we can't launch in split screen mode or the stack we are launching // doesn't support split-screen mode, go ahead an dismiss split-screen and display a // warning toast about it. - mAtmService.getTaskChangeNotificationController().notifyActivityDismissingDockedStack(); + mAtmService.getTaskChangeNotificationController() + .notifyActivityDismissingDockedStack(); final ActivityStack primarySplitStack = display.getSplitScreenPrimaryStack(); primarySplitStack.setWindowingModeInSurfaceTransaction(WINDOWING_MODE_UNDEFINED, false /* animate */, false /* showRecents */, @@ -972,7 +946,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn false /* preserveWindows */, true /* deferResume */); } } finally { - if (showRecents && !alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY + if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { // Make sure recents stack exist when creating a dock stack as it normally needs to // be on the other side of the docked stack and we make visibility decisions based @@ -1029,10 +1003,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return getDisplayContent(); } - int getDisplayId() { - return mDisplayId; - } - /** * Defers updating the bounds of the stack. If the stack was resized/repositioned while * deferring, the bounds will update in {@link #continueUpdateBounds()}. @@ -1138,10 +1108,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return r.getTask().mTaskId != taskId && r.appToken != notTop && r.canBeTopRunning(); } - ActivityRecord getTopNonFinishingActivity() { - return getTopActivity(false /*includeFinishing*/, true /*includeOverlays*/); - } - ActivityRecord isInStackLocked(IBinder token) { final ActivityRecord r = ActivityRecord.forTokenLocked(token); return isInStackLocked(r); @@ -1172,11 +1138,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } final boolean isOnHomeDisplay() { - return mDisplayId == DEFAULT_DISPLAY; - } - - private boolean returnsToHomeStack() { - return !inMultiWindowMode() && hasChild() && getBottomMostTask().returnsToHomeStack(); + return getDisplayId() == DEFAULT_DISPLAY; } void moveToFront(String reason) { @@ -1280,11 +1242,11 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn super.switchUser(userId); forAllTasks((t) -> { - if (t.mWmService.isCurrentProfile(t.mUserId) || t.showForAllUsers()) { + if (t.showToCurrentUser()) { mChildren.remove(t); mChildren.add(t); } - }); + }, true /* traverseTopToBottom */, this); } void minimalResumeActivityLocked(ActivityRecord r) { @@ -1609,45 +1571,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS); } - /** - * Returns true if the stack is translucent and can have other contents visible behind it if - * needed. A stack is considered translucent if it don't contain a visible or - * starting (about to be visible) activity that is fullscreen (opaque). - * @param starting The currently starting activity or null if there is none. - */ - @VisibleForTesting - boolean isStackTranslucent(ActivityRecord starting) { - if (!isAttached() || mForceHidden) { - return true; - } - final PooledPredicate p = PooledLambda.obtainPredicate(ActivityStack::isOpaqueActivity, - PooledLambda.__(ActivityRecord.class), starting); - final ActivityRecord opaque = getActivity(p); - p.recycle(); - return opaque == null; - } - - private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) { - if (r.finishing) { - // We don't factor in finishing activities when determining translucency since - // they will be gone soon. - return false; - } - - if (!r.visibleIgnoringKeyguard && r != starting) { - // Also ignore invisible activities that are not the currently starting - // activity (about to be visible). - return false; - } - - if (r.occludesParent() || r.hasWallpaper) { - // Stack isn't translucent if it has at least one fullscreen activity - // that is visible. - return true; - } - return false; - } - boolean isTopStackOnDisplay() { final DisplayContent display = getDisplay(); return display != null && display.isTopStack(this); @@ -1667,15 +1590,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return topActivity != null && topActivity.mVisibleRequested; } - /** - * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't - * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking - * the first one is ok. - */ - boolean isControlledByTaskOrganizer() { - return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null; - } - private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) { tr.setTaskOrganizer(organizer); } @@ -1690,7 +1604,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStack::transferSingleTaskToOrganizer, PooledLambda.__(Task.class), organizer); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -1699,6 +1613,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn * * @param starting The currently starting activity or null if there is none. */ + @Override boolean shouldBeVisible(ActivityRecord starting) { return getVisibility(starting) != STACK_VISIBILITY_INVISIBLE; } @@ -1756,7 +1671,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn break; } } - if (other.isStackTranslucent(starting)) { + if (other.isTranslucent(starting)) { // Can be visible behind a translucent fullscreen stack. gotTranslucentFullscreen = true; continue; @@ -1765,7 +1680,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !gotOpaqueSplitScreenPrimary) { gotSplitScreenStack = true; - gotTranslucentSplitScreenPrimary = other.isStackTranslucent(starting); + gotTranslucentSplitScreenPrimary = other.isTranslucent(starting); gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary; if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && gotOpaqueSplitScreenPrimary) { @@ -1775,7 +1690,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY && !gotOpaqueSplitScreenSecondary) { gotSplitScreenStack = true; - gotTranslucentSplitScreenSecondary = other.isStackTranslucent(starting); + gotTranslucentSplitScreenSecondary = other.isTranslucent(starting); gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary; if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY && gotOpaqueSplitScreenSecondary) { @@ -1876,13 +1791,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return inPinnedWindowingMode(); } - @Override - public boolean supportsSplitScreenWindowingMode() { - final Task topTask = getTopMostTask(); - return super.supportsSplitScreenWindowingMode() - && (topTask == null || topTask.supportsSplitScreenWindowingMode()); - } - + // TODO(NOW!) /** * Returns {@code true} if this is the top-most split-screen-primary or * split-screen-secondary stack, {@code false} otherwise. @@ -1918,7 +1827,9 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn * @return true if {@param r} is visible taken Keyguard state into account, false otherwise */ boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible, boolean isTop) { - final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY; + int displayId = getDisplayId(); + if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY; + final boolean keyguardOrAodShowing = mStackSupervisor.getKeyguardController() .isKeyguardOrAodShowing(displayId); final boolean keyguardLocked = mStackSupervisor.getKeyguardController().isKeyguardLocked(); @@ -2084,24 +1995,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return result; } - /** - * Returns the currently resumed activity. - */ - protected ActivityRecord getResumedActivity() { - return mResumedActivity; - } - - private void setResumedActivity(ActivityRecord r, String reason) { - if (mResumedActivity == r) { - return; - } - - if (DEBUG_STACK) Slog.d(TAG_STACK, "setResumedActivity stack:" + this + " + from: " - + mResumedActivity + " to:" + r + " reason:" + reason); - mResumedActivity = r; - mStackSupervisor.updateTopResumedActivityIfNeeded(); - } - @GuardedBy("mService") private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { if (!mAtmService.isBooting() && !mAtmService.isBooted()) { @@ -2412,7 +2305,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn // result of invisible window resize. // TODO: Remove this once visibilities are set correctly immediately when // starting an activity. - notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayId, + notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(), true /* markFrozenIfConfigChanged */, false /* deferResume */); } @@ -2553,7 +2446,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home"); - return mRootWindowContainer.resumeHomeActivity(prev, reason, mDisplayId); + return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayId()); } void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, @@ -2827,7 +2720,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn void finishVoiceTask(IVoiceInteractionSession session) { final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::finishIfVoiceTask, PooledLambda.__(Task.class), session.asBinder()); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3020,29 +2913,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return foundParentInTask; } - /** - * Remove any state associated with the {@link ActivityRecord}. This should be called whenever - * an activity moves away from the stack. - */ - void onActivityRemovedFromStack(ActivityRecord r) { - r.removeTimeouts(); - - mExitingActivities.remove(r); - - if (mResumedActivity != null && mResumedActivity == r) { - setResumedActivity(null, "onActivityRemovedFromStack"); - } - if (mPausingActivity != null && mPausingActivity == r) { - mPausingActivity = null; - } - } - - void onActivityAddedToStack(ActivityRecord r) { - if (r.isState(RESUMED)) { - setResumedActivity(r, "onActivityAddedToStack"); - } - } - void removeLaunchTickMessages() { forAllActivities(ActivityRecord::removeLaunchTickRunnable); } @@ -3141,7 +3011,8 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mRootWindowContainer.resumeFocusedStacksTopActivities(); } EventLogTags.writeWmTaskToFront(tr.mUserId, tr.mTaskId); - mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(tr.getTaskInfo()); + mAtmService.getTaskChangeNotificationController() + .notifyTaskMovedToFront(tr.getTaskInfo()); } finally { getDisplay().continueUpdateImeTarget(); } @@ -3239,7 +3110,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStack::processTaskResizeBounds, PooledLambda.__(Task.class), taskBounds, tempTaskInsetBounds); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); setBounds(bounds); @@ -3276,7 +3147,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds, PooledLambda.__(Task.class), bounds); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3292,7 +3163,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskDisplayedBounds, PooledLambda.__(Task.class), bounds); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3384,7 +3255,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn private boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient, String dumpPackage, boolean needSep) { - if (!hasChild()) { return false; } @@ -3403,11 +3273,11 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final ArrayList<ActivityRecord> activities = new ArrayList<>(); // Add activities by traversing the hierarchy from bottom to top, since activities // are dumped in reverse order in {@link ActivityStackSupervisor#dumpHistoryList()}. - forAllActivities((Consumer<ActivityRecord>) activities::add, + task.forAllActivities((Consumer<ActivityRecord>) activities::add, false /* traverseTopToBottom */); dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient, dumpPackage, false, null, task); - }); + }, true /* traverseTopToBottom */, this); return true; } @@ -3458,49 +3328,9 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } } - /** - * Removes the input task from this stack. - * - * @param child to remove. - * @param reason for removal. - */ - void removeChild(WindowContainer child, String reason) { - if (!mChildren.contains(child)) { - // Not really in this stack anymore... - return; - } - - final DisplayContent display = getDisplay(); - if (DEBUG_TASK_MOVEMENT) { - Slog.d(TAG_WM, "removeChild: task=" + child + " reason=" + reason); - } - - super.removeChild(child); - - EventLogTags.writeWmRemoveTask(((Task) child).mTaskId, mStackId); - - if (display.isSingleTaskInstance()) { - mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId); - } - - display.mDisplayContent.setLayoutNeeded(); - - if (!hasChild()) { - // Stack is now empty... - removeIfPossible(); - } - } - - @Override - void removeChild(WindowContainer child) { - removeChild(child, "removeChild"); - } - - Task createTask(int taskId, ActivityInfo info, Intent intent, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - boolean toTop) { - return createTask(taskId, info, intent, voiceSession, voiceInteractor, toTop, - null /*activity*/, null /*source*/, null /*options*/); + Task createTask(int taskId, ActivityInfo info, Intent intent, boolean toTop) { + return createTask(taskId, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, + toTop, null /*activity*/, null /*source*/, null /*options*/); } Task createTask(int taskId, ActivityInfo info, Intent intent, @@ -3511,7 +3341,8 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mAtmService, taskId, info, intent, voiceSession, voiceInteractor, this); // add the task to stack first, mTaskPositioner might need the stack association addChild(task, toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); - final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY; + int displayId = getDisplayId(); + if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY; final boolean isLockscreenShown = mAtmService.mStackSupervisor.getKeyguardController() .isKeyguardOrAodShowing(displayId); if (!mStackSupervisor.getLaunchParamsController() @@ -3522,14 +3353,25 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return task; } - void addChild(final Task task, final boolean toTop, boolean showForAllUsers) { + void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { if (isSingleTaskInstance() && hasChild()) { throw new IllegalStateException("Can only have one child on stack=" + this); } - // We only want to move the parents to the parents if we are creating this task at the - // top of its stack. - addChild(task, toTop ? MAX_VALUE : 0, showForAllUsers, toTop /*moveParents*/); + Task task = child.asTask(); + try { + + if (task != null) { + task.setForceShowForAllUsers(showForAllUsers); + } + // We only want to move the parents to the parents if we are creating this task at the + // top of its stack. + addChild(child, toTop ? MAX_VALUE : 0, toTop /*moveParents*/); + } finally { + if (task != null) { + task.setForceShowForAllUsers(false); + } + } } void positionChildAt(Task task, int position) { @@ -3747,16 +3589,12 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStackSupervisor::updatePictureInPictureMode, mStackSupervisor, PooledLambda.__(Task.class), targetStackBounds, forceUpdate); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } - public int getStackId() { - return mStackId; - } - void prepareFreezingTaskBounds() { - forAllTasks(Task::prepareFreezingBounds); + forAllTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */, this); } /** @@ -3788,7 +3626,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer(Task::alignToAdjustedBounds, PooledLambda.__(Task.class), adjusted ? mAdjustedBounds : getRawBounds(), insetBounds, alignBottom); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3798,7 +3636,13 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn @Override public int setBounds(Rect bounds) { - return setBounds(getRequestedOverrideBounds(), bounds); + // Calling Task#setBounds() for leaf task since this is the a specialization of + // {@link #setBounds(int)} for ActivityStack. + if (!isRootTask()) { + return super.setBounds(bounds); + } else { + return setBounds(getRequestedOverrideBounds(), bounds); + } } private int setBounds(Rect existing, Rect bounds) { @@ -4038,30 +3882,15 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn * Put a Task in this stack. Used for adding only. * When task is added to top of the stack, the entire branch of the hierarchy (including stack * and display) will be brought to top. - * @param task The task to add. + * @param child The child to add. * @param position Target position to add the task to. - * @param showForAllUsers Whether to show the task regardless of the current user. */ - private void addChild(Task task, int position, boolean showForAllUsers, boolean moveParents) { - try { - // Force show for all user so task can be position correctly based on which user is - // active. We clear the force show below. - task.setForceShowForAllUsers(showForAllUsers); - // Add child task. - addChild(task, null); - - // Move child to a proper position, as some restriction for position might apply. - positionChildAt(position, task, moveParents /* includingParents */); - - } finally { - task.setForceShowForAllUsers(false); - } - } + private void addChild(WindowContainer child, int position, boolean moveParents) { + // Add child task. + addChild(child, null); - @Override - void addChild(WindowContainer child, int position) { - final Task task = (Task) child; - addChild(task, position, task.showForAllUsers(), false /* includingParents */); + // Move child to a proper position, as some restriction for position might apply. + positionChildAt(position, child, moveParents /* includingParents */); } void positionChildAtTop(Task child) { @@ -4099,29 +3928,18 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } @Override - void positionChildAt(int position, WindowContainer child, boolean includingParents) { - final Task task = (Task) child; - final int targetPosition = findPositionForTask(task, position); - super.positionChildAt(targetPosition, child, includingParents); - - // Log positioning. - if (DEBUG_TASK_MOVEMENT) { - Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position); - } - - final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0; - EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, targetPosition); - } - - @Override void onChildPositionChanged(WindowContainer child) { if (!mChildren.contains(child)) { return; } - final Task task = (Task) child; - final boolean isTop = getTopChild() == task; - task.updateTaskMovement(isTop); + final boolean isTop = getTopChild() == child; + + final Task task = child.asTask(); + if (task != null) { + task.updateTaskMovement(isTop); + } + if (isTop) { final DisplayContent displayContent = getDisplayContent(); displayContent.layoutAndAssignWindowLayersIfNeeded(); @@ -4129,34 +3947,17 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } @Override - protected void onParentChanged( - ConfigurationContainer newParent, ConfigurationContainer oldParent) { + void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { final DisplayContent display = newParent != null ? ((WindowContainer) newParent).getDisplayContent() : null; final DisplayContent oldDisplay = oldParent != null ? ((WindowContainer) oldParent).getDisplayContent() : null; - mDisplayId = (display != null) ? display.mDisplayId : INVALID_DISPLAY; - mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY; - - if (display != null) { - // Rotations are relative to the display. This means if there are 2 displays rotated - // differently (eg. 2 monitors with one landscape and one portrait), moving a stack - // from one to the other could look like a rotation change. To prevent this - // apparent rotation change (and corresponding bounds rotation), pretend like our - // current rotation is already the same as the new display. - // Note, if ActivityStack or related logic ever gets nested, this logic will need - // to move to onConfigurationChanged. - getConfiguration().windowConfiguration.setRotation( - display.getWindowConfiguration().getRotation()); - } super.onParentChanged(newParent, oldParent); - if (getParent() == null && mDisplayContent != null) { - EventLogTags.writeWmStackRemoved(mStackId); - mDisplayContent = null; - mWmService.mWindowPlacerLocked.requestTraversal(); - } - if (display != null && inSplitScreenPrimaryWindowingMode()) { + + if (display != null && inSplitScreenPrimaryWindowingMode() + // only do this for the base stack + && !newParent.inSplitScreenPrimaryWindowingMode()) { // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */, @@ -4164,7 +3965,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect, mTmpRect2, null, null, PRESERVE_WINDOWS); } - mRootWindowContainer.updateUIDsPresentOnDisplay(); // Resume next focusable stack after reparenting to another display if we aren't removing // the prevous display. @@ -4178,68 +3978,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn newParent.moveStackToDisplay(this, onTop); } - // TODO: We should really have users as a window container in the hierarchy so that we don't - // have to do complicated things like we are doing in this method. - int findPositionForTask(Task task, int targetPosition) { - final boolean canShowTask = task.showToCurrentUser(); - - final int stackSize = mChildren.size(); - int minPosition = 0; - int maxPosition = stackSize - 1; - - if (canShowTask) { - minPosition = computeMinPosition(minPosition, stackSize); - } else { - maxPosition = computeMaxPosition(maxPosition); - } - - // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid. - if (targetPosition == POSITION_BOTTOM && minPosition == 0) { - return POSITION_BOTTOM; - } else if (targetPosition == POSITION_TOP && maxPosition == (stackSize - 1)) { - return POSITION_TOP; - } - // Reset position based on minimum/maximum possible positions. - return Math.min(Math.max(targetPosition, minPosition), maxPosition); - } - - /** Calculate the minimum possible position for a task that can be shown to the user. - * The minimum position will be above all other tasks that can't be shown. - * @param minPosition The minimum position the caller is suggesting. - * We will start adjusting up from here. - * @param size The size of the current task list. - */ - // TODO(task-hierarchy): Move user to their own window container. - private int computeMinPosition(int minPosition, int size) { - while (minPosition < size) { - final Task tmpTask = (Task) mChildren.get(minPosition); - final boolean canShowTmpTask = tmpTask.showToCurrentUser(); - if (canShowTmpTask) { - break; - } - minPosition++; - } - return minPosition; - } - - /** Calculate the maximum possible position for a task that can't be shown to the user. - * The maximum position will be below all other tasks that can be shown. - * @param maxPosition The maximum position the caller is suggesting. - * We will start adjusting down from here. - */ - // TODO(task-hierarchy): Move user to their own window container. - private int computeMaxPosition(int maxPosition) { - while (maxPosition > 0) { - final Task tmpTask = (Task) mChildren.get(maxPosition); - final boolean canShowTmpTask = tmpTask.showToCurrentUser(); - if (!canShowTmpTask) { - break; - } - maxPosition--; - } - return maxPosition; - } - private void updateSurfaceBounds() { updateSurfaceSize(getPendingTransaction()); updateSurfacePosition(); @@ -4456,15 +4194,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn false /* deferResume */); } - @Override - void removeIfPossible() { - if (isAnimating(TRANSITION | CHILDREN)) { - mDeferRemoval = true; - return; - } - removeImmediately(); - } - /** * Adjusts the stack bounds if the IME is visible. * @@ -4568,12 +4297,14 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn t.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER); t.setWaitingForDrawnIfResizingChanged(); } - }); + }, true /* traverseTopToBottom */, this); } /** Resets the resizing state of all windows. */ void endImeAdjustAnimation() { - forAllTasks((t) -> { t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); }); + forAllTasks((t) -> { + t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); + }, true /* traverseTopToBottom */, this); } private int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) { @@ -4751,14 +4482,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return mMinimizeAmount != 0f; } - /** - * @return {@code true} if we have a {@link Task} that is animating (currently only used for the - * recents animation); {@code false} otherwise. - */ - boolean isTaskAnimating() { - return getTask(Task::isTaskAnimating) != null; - } - @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { pw.println(prefix + "mStackId=" + mStackId); @@ -4792,11 +4515,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix); } - @Override - boolean fillsParent() { - return matchParentBounds(); - } - String getName() { return toShortString(); } @@ -4996,7 +4714,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn /** Called immediately prior to resizing the tasks at the end of the pinned stack animation. */ void onPipAnimationEndResize() { mBoundsAnimating = false; - forAllTasks(Task::clearPreserveNonFloatingState, false); + forAllTasks(Task::clearPreserveNonFloatingState, false /* traverseTopToBottom */, this); mWmService.requestTraversal(); } @@ -5096,24 +4814,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } @Override - Dimmer getDimmer() { - return mDimmer; - } - - @Override - void prepareSurfaces() { - mDimmer.resetDimStates(); - super.prepareSurfaces(); - getDimBounds(mTmpDimBoundsRect); - - // Bounds need to be relative, as the dim layer is a child. - mTmpDimBoundsRect.offsetTo(0, 0); - if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { - scheduleAnimation(); - } - } - - @Override public boolean setPinnedStackAlpha(float alpha) { // Hold the lock since this is called from the BoundsAnimator running on the UiThread synchronized (mWmService.mGlobalLock) { @@ -5132,49 +4832,10 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return mDisplayContent.getDisplayInfo(); } - void dim(float alpha) { - mDimmer.dimAbove(getPendingTransaction(), alpha); - scheduleAnimation(); - } - - void stopDimming() { - mDimmer.stopDim(getPendingTransaction()); - scheduleAnimation(); - } - AnimatingActivityRegistry getAnimatingActivityRegistry() { return mAnimatingActivityRegistry; } - @Override - void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets, - Rect outSurfaceInsets) { - final Task task = getTopMostTask(); - if (task != null) { - task.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); - } else { - super.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); - } - } - - @Override - RemoteAnimationTarget createRemoteAnimationTarget( - RemoteAnimationController.RemoteAnimationRecord record) { - final Task task = getTopMostTask(); - return task != null ? task.createRemoteAnimationTarget(record) : null; - } - - @Override - public String toString() { - return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this)) - + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode()) - + " visible=" + shouldBeVisible(null /* starting */) - + " translucent=" + isStackTranslucent(null /* starting */) - + ", " - + getChildCount() + " tasks}"; - } - void executeAppTransition(ActivityOptions options) { getDisplay().mDisplayContent.executeAppTransition(); ActivityOptions.abort(options); @@ -5197,6 +4858,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return shouldSleepActivities() || mAtmService.mShuttingDown; } + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { final long token = proto.start(fieldId); @@ -5204,12 +4866,12 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn proto.write(com.android.server.am.ActivityStackProto.ID, mStackId); forAllTasks((t) -> { - t.dumpDebug(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel); - }); + t.dumpDebugInner(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel); + }, true /* traverseTopToBottom */, this); if (mResumedActivity != null) { mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY); } - proto.write(DISPLAY_ID, mDisplayId); + proto.write(DISPLAY_ID, getDisplayId()); if (!matchParentBounds()) { final Rect bounds = getRequestedOverrideBounds(); bounds.dumpDebug(proto, com.android.server.am.ActivityStackProto.BOUNDS); @@ -5230,7 +4892,9 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final long token = proto.start(fieldId); super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); proto.write(StackProto.ID, mStackId); - forAllTasks((t) -> { t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel); }); + forAllTasks((t) -> { + t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel); + }, true /* traverseTopToBottom */, this); proto.write(FILLS_PARENT, matchParentBounds()); getRawBounds().dumpDebug(proto, StackProto.BOUNDS); proto.write(DEFER_REMOVAL, mDeferRemoval); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 8ef01e3f4776..f2ce7e86bc3b 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -419,7 +419,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final PooledConsumer c = PooledLambda.obtainConsumer( MoveTaskToFullscreenHelper::processTask, this, PooledLambda.__(Task.class)); - fromStack.forAllTasks(c, false); + fromStack.forAllTasks(c, false /* traverseTopToBottom */, fromStack); c.recycle(); mToDisplay = null; mTopTask = null; @@ -1724,7 +1724,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } else { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class)); - stack.forAllTasks(c); + stack.forAllTasks(c, true /* traverseTopToBottom */, stack); c.recycle(); } } @@ -1849,14 +1849,14 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { boolean restoreRecentTaskLocked(Task task, ActivityOptions aOptions, boolean onTop) { final ActivityStack stack = mRootWindowContainer.getLaunchStack(null, aOptions, task, onTop); - final ActivityStack currentStack = task.getStack(); + final WindowContainer parent = task.getParent(); - if (currentStack == stack) { + if (parent == stack) { // Nothing else to do since it is already restored in the right stack. return true; } - if (currentStack != null) { + if (parent != null) { // Task has already been restored once. Just re-parent it to the new stack. task.reparent(stack, POSITION_TOP, true /*moveParents*/, "restoreRecentTaskLocked"); return true; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 47e8b87dcf08..8491bc2c4756 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -306,7 +306,7 @@ import java.util.Set; */ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM; - private static final String TAG_STACK = TAG + POSTFIX_STACK; + static final String TAG_STACK = TAG + POSTFIX_STACK; static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; @@ -2057,8 +2057,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public int getDisplayId(IBinder activityToken) throws RemoteException { synchronized (mGlobalLock) { final ActivityStack stack = ActivityRecord.getStackLocked(activityToken); - if (stack != null && stack.getDisplayId() != INVALID_DISPLAY) { - return stack.getDisplayId(); + if (stack != null) { + final int displayId = stack.getDisplayId(); + return displayId != INVALID_DISPLAY ? displayId : DEFAULT_DISPLAY; } return DEFAULT_DISPLAY; } @@ -3216,8 +3217,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityStack stack = r.getActivityStack(); final Task task = stack.createTask( - mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent, - null /* voiceSession */, null /* voiceInteractor */, !ON_TOP); + mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent, !ON_TOP); if (!mRecentTasks.addToBottom(task)) { // The app has too many tasks already and we can't add any more stack.removeChild(task, "addAppTask"); @@ -4385,7 +4385,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (params.hasSetAspectRatio() && !mWindowManager.isValidPictureInPictureAspectRatio( - r.getDisplayId(), params.getAspectRatio())) { + r.getDisplay(), params.getAspectRatio())) { final float minAspectRatio = mContext.getResources().getFloat( com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio); final float maxAspectRatio = mContext.getResources().getFloat( diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 32802eed94f4..dbc99114c031 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -69,7 +69,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFI import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 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; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; @@ -167,8 +166,10 @@ import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; +import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -306,8 +307,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // on the IME target. We mainly have this container grouping so we can keep track of all the IME // window containers together and move them in-sync if/when needed. We use a subclass of // WindowContainer which is omitted from screen magnification, as the IME is never magnified. - private final NonAppWindowContainers mImeWindowsContainers = - new NonAppWindowContainers("mImeWindowsContainers", mWmService); + // TODO(display-area): is "no magnification" in the comment still true? + private final ImeContainer mImeWindowsContainers = new ImeContainer(mWmService); private WindowState mTmpWindow; private WindowState mTmpWindow2; @@ -400,14 +401,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private int mCurrentOverrideConfigurationChanges; /** - * Orientation forced by some window. If there is no visible window that specifies orientation - * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}. - * - * @see NonAppWindowContainers#getOrientation() - */ - private int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - - /** * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not * occluded. * @@ -1282,11 +1275,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mDisplayRotation.getLastOrientation(); } - @ScreenOrientation - int getLastWindowForcedOrientation() { - return mLastWindowForcedOrientation; - } - void registerRemoteAnimations(RemoteAnimationDefinition definition) { mAppTransitionController.registerRemoteAnimations(definition); } @@ -2026,6 +2014,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTaskStackContainers.getVisibleTasks(); } + SurfaceControl getSplitScreenDividerAnchor() { + return mTaskStackContainers.getSplitScreenDividerAnchor(); + } + void onStackWindowingModeChanged(ActivityStack stack) { mTaskStackContainers.onStackWindowingModeChanged(stack); } @@ -2112,7 +2104,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } boolean forAllImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { - return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom); + return mImeWindowsContainers.forAllWindowForce(callback, traverseTopToBottom); } /** @@ -2129,16 +2121,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } if (mWmService.mDisplayFrozen) { - if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Display id=%d is frozen, return %d", mDisplayId, - mLastWindowForcedOrientation); - // If the display is frozen, some activities may be in the middle of restarting, and - // thus have removed their old window. If the window has the flag to hide the lock - // screen, then the lock screen can re-appear and inflict its own orientation on us. - // Keep the orientation stable until this all settles down. - return mLastWindowForcedOrientation; - } else if (policy.isKeyguardLocked()) { + if (policy.isKeyguardLocked()) { // Use the last orientation the while the display is frozen with the keyguard // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED // window. We don't want to check the show when locked window directly though as @@ -2149,11 +2132,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDisplayId, getLastOrientation()); return getLastOrientation(); } - } else { - final int orientation = mAboveAppWindowsContainers.getOrientation(); - if (orientation != SCREEN_ORIENTATION_UNSET) { - return orientation; - } + } + final int orientation = mAboveAppWindowsContainers.getOrientation(); + if (orientation != SCREEN_ORIENTATION_UNSET) { + return orientation; } // Top system windows are not requesting an orientation. Start searching from apps. @@ -2370,11 +2352,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo throw new UnsupportedOperationException("See DisplayChildWindowContainer"); } + void positionDisplayAt(int position, boolean includingParents) { + getParent().positionChildAt(position, this, includingParents); + } + @Override void positionChildAt(int position, DisplayChildWindowContainer child, boolean includingParents) { // Children of the display are statically ordered, so the real intention here is to perform // the operation on the display and not the static direct children. - getParent().positionChildAt(position, this, includingParents); + positionDisplayAt(position, includingParents); } void positionStackAt(int position, ActivityStack child, boolean includingParents) { @@ -4132,6 +4118,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo DisplayChildWindowContainer(WindowManagerService service) { super(service); + // TODO(display-area): move to ConfigurationContainer? + mOrientation = SCREEN_ORIENTATION_UNSET; } @Override @@ -4243,7 +4231,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo ArrayList<Task> getVisibleTasks() { final ArrayList<Task> visibleTasks = new ArrayList<>(); forAllTasks(task -> { - if (task.isVisible()) { + if (!task.isRootTask() && task.isVisible()) { visibleTasks.add(task); } }); @@ -4262,36 +4250,48 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private void addStackReferenceIfNeeded(ActivityStack stack) { if (stack.isActivityTypeHome()) { if (mHomeStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" - + mHomeStack + " already exist on display=" + this + " stack=" + stack); - + if (!stack.isDescendantOf(mHomeStack)) { + throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" + + mHomeStack + " already exist on display=" + this + + " stack=" + stack); + } + } else { + mHomeStack = stack; } - mHomeStack = stack; } else if (stack.isActivityTypeRecents()) { if (mRecentsStack != null && mRecentsStack != stack) { - throw new IllegalArgumentException( - "addStackReferenceIfNeeded: recents stack=" + mRecentsStack - + " already exist on display=" + this + " stack=" + stack); + if (!stack.isDescendantOf(mRecentsStack)) { + throw new IllegalArgumentException( + "addStackReferenceIfNeeded: recents stack=" + mRecentsStack + + " already exist on display=" + this + " stack=" + stack); + } + } else { + mRecentsStack = stack; } - mRecentsStack = stack; } final int windowingMode = stack.getWindowingMode(); if (windowingMode == WINDOWING_MODE_PINNED) { if (mPinnedStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack=" - + mPinnedStack + " already exist on display=" + this - + " stack=" + stack); + if (!stack.isDescendantOf(mPinnedStack)) { + throw new IllegalArgumentException( + "addStackReferenceIfNeeded: pinned stack=" + mPinnedStack + + " already exist on display=" + this + " stack=" + stack); + } + } else { + mPinnedStack = stack; } - mPinnedStack = stack; } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { if (mSplitScreenPrimaryStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded:" - + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack - + " already exist on display=" + this + " stack=" + stack); + if (!stack.isDescendantOf(mSplitScreenPrimaryStack)) { + throw new IllegalArgumentException("addStackReferenceIfNeeded:" + + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack + + " already exist on display=" + this + " stack=" + stack); + } + } else { + mSplitScreenPrimaryStack = stack; + mDisplayContent.onSplitScreenModeActivated(); + mDividerControllerLocked.notifyDockedStackExistsChanged(true); } - mSplitScreenPrimaryStack = stack; - mDisplayContent.onSplitScreenModeActivated(); - mDividerControllerLocked.notifyDockedStackExistsChanged(true); } } @@ -4340,8 +4340,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void positionChildAt(int position, ActivityStack child, boolean includingParents) { - if (child.getWindowConfiguration().isAlwaysOnTop() - && position != POSITION_TOP && position != mChildren.size()) { + final boolean moveToTop = (position == POSITION_TOP || position == getChildCount()); + final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0); + if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) { // This stack is always-on-top, override the default behavior. Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom"); @@ -4357,18 +4358,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo includingParents = false; } final int targetPosition = findPositionForStack(position, child, false /* adding */); - super.positionChildAt(targetPosition, child, includingParents); - - if (includingParents) { - // We still want to move the display of this stack container to top because even the - // target position is adjusted to non-top, the intention of the condition is to have - // higher z-order to gain focus (e.g. moving a task of a fullscreen stack to front - // in a non-top display which is using picture-in-picture mode). - final int topChildPosition = getChildCount() - 1; - if (targetPosition < topChildPosition && position >= topChildPosition) { - getParent().positionChildAt(POSITION_TOP, this /* child */, - true /* includingParents */); - } + super.positionChildAt(targetPosition, child, false /* includingParents */); + + if (includingParents && (moveToTop || moveToBottom)) { + // The DisplayContent children do not re-order, but we still want to move the + // display of this stack container because the intention of positioning is to have + // higher z-order to gain focus. + positionDisplayAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM, + true /* includingParents */); } setLayoutNeeded(); @@ -4529,7 +4526,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } @Override - int getOrientation() { + int getOrientation(int candidate) { if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { // Apps and their containers are not allowed to specify an orientation while the // docked stack is visible...except for the home stack if the docked stack is @@ -4547,7 +4544,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return SCREEN_ORIENTATION_UNSPECIFIED; } - final int orientation = super.getOrientation(); + final int orientation = super.getOrientation(candidate); if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) { ProtoLog.v(WM_DEBUG_ORIENTATION, @@ -4688,38 +4685,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } @Override - SurfaceControl.Builder makeChildSurface(WindowContainer child) { - final SurfaceControl.Builder builder = super.makeChildSurface(child); - if (child instanceof WindowToken && ((WindowToken) child).mRoundedCornerOverlay) { - // To draw above the ColorFade layer during the screen off transition, the - // rounded corner overlays need to be at the root of the surface hierarchy. - // TODO: move the ColorLayer into the display overlay layer such that this is not - // necessary anymore. - builder.setParent(null); - } - return builder; - } - - @Override void assignChildLayers(SurfaceControl.Transaction t) { - assignChildLayers(t, null /* imeContainer */); - } - - void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) { - boolean needAssignIme = imeContainer != null - && imeContainer.getSurfaceControl() != null; + boolean needAssignIme = mImeWindowsContainers.getSurfaceControl() != null; for (int j = 0; j < mChildren.size(); ++j) { final WindowToken wt = mChildren.get(j); - // See {@link mSplitScreenDividerAnchor} - if (wt.windowType == TYPE_DOCK_DIVIDER) { - wt.assignRelativeLayer(t, mTaskStackContainers.getSplitScreenDividerAnchor(), 1); - continue; - } - if (wt.mRoundedCornerOverlay) { - wt.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); - continue; - } wt.assignLayer(t, j); wt.assignChildLayers(t); @@ -4728,13 +4698,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (needAssignIme && layer >= mWmService.mPolicy.getWindowLayerFromTypeLw( TYPE_INPUT_METHOD_DIALOG, true)) { - imeContainer.assignRelativeLayer(t, wt.getSurfaceControl(), -1); + mImeWindowsContainers.assignRelativeLayer(t, wt.getSurfaceControl(), -1); needAssignIme = false; } } - if (needAssignIme) { - imeContainer.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE); - } } } @@ -4748,6 +4715,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void assignChildLayers(SurfaceControl.Transaction t) { + mImeWindowsContainers.setNeedsLayer(); mBelowAppWindowsContainers.assignLayer(t, 0); mTaskStackContainers.assignLayer(t, 1); mAboveAppWindowsContainers.assignLayer(t, 2); @@ -4783,15 +4751,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // TODO: We need to use an extra level on the app surface to ensure // this is always above SurfaceView but always below attached window. 1); - needAssignIme = false; } // Above we have assigned layers to our children, now we ask them to assign // layers to their children. mBelowAppWindowsContainers.assignChildLayers(t); mTaskStackContainers.assignChildLayers(t); - mAboveAppWindowsContainers.assignChildLayers(t, - needAssignIme ? mImeWindowsContainers : null); + mAboveAppWindowsContainers.assignChildLayers(t); + mImeWindowsContainers.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE); mImeWindowsContainers.assignChildLayers(t); } @@ -4807,47 +4774,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo addChild(mImeWindowsContainers, null); } - /** - * In split-screen mode we process the IME containers above the docked divider - * rather than directly above their target. - */ - private boolean skipTraverseChild(WindowContainer child) { - return child == mImeWindowsContainers && mInputMethodTarget != null - && !hasSplitScreenPrimaryStack(); - } - - @Override - boolean forAllWindows(ToBooleanFunction<WindowState> callback, - boolean traverseTopToBottom) { - // Special handling so we can process IME windows with #forAllImeWindows above their IME - // target, or here in order if there isn't an IME target. - if (traverseTopToBottom) { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer child = mChildren.get(i); - if (skipTraverseChild(child)) { - continue; - } - - if (child.forAllWindows(callback, traverseTopToBottom)) { - return true; - } - } - } else { - final int count = mChildren.size(); - for (int i = 0; i < count; i++) { - final WindowContainer child = mChildren.get(i); - if (skipTraverseChild(child)) { - continue; - } - - if (child.forAllWindows(callback, traverseTopToBottom)) { - return true; - } - } - } - return false; - } - @Override void positionChildAt(int position, WindowContainer child, boolean includingParents) { // Children of the WindowContainers are statically ordered, so the real intention here @@ -4873,11 +4799,25 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo token2.mOwnerCanManageAppTokens) ? -1 : 1; private final Predicate<WindowState> mGetOrientingWindow = w -> { - if (!w.isVisibleLw() || !w.mLegacyPolicyVisibilityAfterAnim) { - return false; + final WindowManagerPolicy policy = mWmService.mPolicy; + if (policy.isKeyguardHostWindow(w.mAttrs)) { + if (mWmService.mKeyguardGoingAway) { + return false; + } + // Consider unoccluding only when all unknown visibilities have been + // resolved, as otherwise we just may be starting another occluding activity. + final boolean isUnoccluding = + mDisplayContent.mAppTransition.getAppTransition() + == TRANSIT_KEYGUARD_UNOCCLUDE + && mDisplayContent.mUnknownAppVisibilityController.allResolved(); + // If keyguard is showing, or we're unoccluding, force the keyguard's orientation, + // even if SystemUI hasn't updated the attrs yet. + if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) { + return true; + } } final int req = w.mAttrs.screenOrientation; - if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND + if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND || req == SCREEN_ORIENTATION_UNSET) { return false; } @@ -4904,39 +4844,27 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } @Override - int getOrientation() { - final WindowManagerPolicy policy = mWmService.mPolicy; + int getOrientation(int candidate) { // Find a window requesting orientation. final WindowState win = getWindow(mGetOrientingWindow); if (win != null) { - final int req = win.mAttrs.screenOrientation; - if (policy.isKeyguardHostWindow(win.mAttrs)) { - mLastKeyguardForcedOrientation = req; - if (mWmService.mKeyguardGoingAway) { - // Keyguard can't affect the orientation if it is going away... - mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - return SCREEN_ORIENTATION_UNSET; - } - } + int req = win.mAttrs.screenOrientation; ProtoLog.v(WM_DEBUG_ORIENTATION, "%s forcing orientation to %d for display id=%d", win, req, mDisplayId); - return (mLastWindowForcedOrientation = req); - } - - mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - - // Only allow force setting the orientation when all unknown visibilities have been - // resolved, as otherwise we just may be starting another occluding activity. - final boolean isUnoccluding = - mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE - && mUnknownAppVisibilityController.allResolved(); - if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) { - return mLastKeyguardForcedOrientation; + if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) { + // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be + // stale. We record / use the last known override. + if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) { + mDisplayContent.mLastKeyguardForcedOrientation = req; + } else { + req = mDisplayContent.mLastKeyguardForcedOrientation; + } + } + return req; } - - return SCREEN_ORIENTATION_UNSET; + return candidate; } @Override @@ -4961,6 +4889,74 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + /** + * Container for IME windows. + * + * This has some special behaviors: + * - layers assignment is ignored except if setNeedsLayer() has been called before (and no + * layer has been assigned since), to facilitate assigning the layer from the IME target, or + * fall back if there is no target. + * - the container doesn't always participate in window traversal, according to + * {@link #skipImeWindowsDuringTraversal()} + */ + private class ImeContainer extends NonAppWindowContainers { + boolean mNeedsLayer = false; + + ImeContainer(WindowManagerService wms) { + super("ImeContainer", wms); + } + + public void setNeedsLayer() { + mNeedsLayer = true; + } + + @Override + int getOrientation(int candidate) { + // IME does not participate in orientation. + return candidate; + } + + @Override + boolean forAllWindows(ToBooleanFunction<WindowState> callback, + boolean traverseTopToBottom) { + final DisplayContent dc = mDisplayContent; + if (skipImeWindowsDuringTraversal(dc)) { + return false; + } + return super.forAllWindows(callback, traverseTopToBottom); + } + + private boolean skipImeWindowsDuringTraversal(DisplayContent dc) { + // We skip IME windows so they're processed just above their target, except + // in split-screen mode where we process the IME containers above the docked divider. + return dc.mInputMethodTarget != null && !dc.hasSplitScreenPrimaryStack(); + } + + /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ + boolean forAllWindowForce(ToBooleanFunction<WindowState> callback, + boolean traverseTopToBottom) { + return super.forAllWindows(callback, traverseTopToBottom); + } + + @Override + void assignLayer(Transaction t, int layer) { + if (!mNeedsLayer) { + return; + } + super.assignLayer(t, layer); + mNeedsLayer = false; + } + + @Override + void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { + if (!mNeedsLayer) { + return; + } + super.assignRelativeLayer(t, relativeTo, layer); + mNeedsLayer = false; + } + } + @Override SurfaceSession getSession() { return mSession; @@ -5064,6 +5060,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * with {@link WindowState#assignLayer} */ void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) { + mImeWindowsContainers.setNeedsLayer(); child.assignRelativeLayer(t, mImeWindowsContainers.getSurfaceControl(), 1); } @@ -5740,6 +5737,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mAtmService.mStackSupervisor.getNextTaskIdForUser(); } + ActivityStack createStack(int windowingMode, int activityType, boolean onTop) { + return createStack(windowingMode, activityType, onTop, null /*info*/, null /*intent*/); + } + /** * Creates a stack matching the input windowing mode and activity type on this display. * @param windowingMode The windowing mode the stack should be created in. If @@ -5751,13 +5752,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @param onTop If true the stack will be created at the top of the display, else at the bottom. * @return The newly created stack. */ - ActivityStack createStack(int windowingMode, int activityType, boolean onTop) { + ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info, + Intent intent) { if (mSingleTaskInstance && getStackCount() > 0) { // Create stack on default display instead since this display can only contain 1 stack. // TODO: Kinda a hack, but better that having the decision at each call point. Hoping // this goes away once ActivityView is no longer using virtual displays. return mRootWindowContainer.getDefaultDisplay().createStack( - windowingMode, activityType, onTop); + windowingMode, activityType, onTop, info, intent); } if (activityType == ACTIVITY_TYPE_UNDEFINED) { @@ -5785,18 +5787,23 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } final int stackId = getNextStackId(); - return createStackUnchecked(windowingMode, activityType, stackId, onTop); + return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent); } @VisibleForTesting ActivityStack createStackUnchecked(int windowingMode, int activityType, - int stackId, boolean onTop) { + int stackId, boolean onTop, ActivityInfo info, Intent intent) { if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) { throw new IllegalArgumentException("Stack with windowing mode cannot with non standard " + "activity type."); } + if (info == null) { + info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + } + final ActivityStack stack = new ActivityStack(this, stackId, - mRootWindowContainer.mStackSupervisor, activityType); + mRootWindowContainer.mStackSupervisor, activityType, info, intent); addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 5df80fcc7c06..e1dfc177a881 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -32,7 +32,6 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.Process.SYSTEM_UID; -import static android.view.Display.DEFAULT_DISPLAY; import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; @@ -1382,7 +1381,7 @@ class RecentTasks { // Ignore tasks from different displays // TODO (b/115289124): No Recents on non-default displays. - if (stack.getDisplayId() != DEFAULT_DISPLAY) { + if (!stack.isOnHomeDisplay()) { return false; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index b255b5eb7c0e..614809505dee 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -374,7 +374,7 @@ public class RecentsAnimationController implements DeathRecipient { final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) -> { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class), visibleTasks); - targetStack.forAllTasks(c); + targetStack.forAllTasks(c, true /* traverseTopToBottom */, targetStack); c.recycle(); } diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index e310fc18b6a7..2f61ca05ada5 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -247,8 +247,7 @@ class ResetTargetTaskHelper { } else { targetTask = mTargetStack.createTask( atmService.mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info, - null /* intent */, null /* voiceSession */, null /* voiceInteractor */, - false /* toTop */); + null /* intent */, false /* toTop */); targetTask.affinityIntent = r.intent; createdTasks.add(targetTask); if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 8b1005e2e847..8202833783a9 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2131,23 +2131,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // We will then perform a windowing mode change for both scenarios. stack = display.createStack( r.getActivityStack().getRequestedOverrideWindowingMode(), - r.getActivityType(), ON_TOP); + r.getActivityType(), ON_TOP, r.info, r.intent); // There are multiple activities in the task and moving the top activity should // reveal/leave the other activities in their original task. - // Currently, we don't support reparenting activities across tasks in two different - // stacks, so instead, just create a new task in the same stack, reparent the - // activity into that task, and then reparent the whole task to the new stack. This - // ensures that all the necessary work to migrate states in the old and new stacks - // is also done. - final Task newTask = task.getStack().createTask( - mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info, - r.intent, null, null, true); + Task newTask = stack.createTask(mStackSupervisor.getNextTaskIdForUser(r.mUserId), + r.info, r.intent, true); r.reparent(newTask, MAX_VALUE, "moveActivityToStack"); - - // Defer resume until below, and do not schedule PiP changes until we animate below - newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, - DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason); } stack.setWindowingMode(WINDOWING_MODE_PINNED); @@ -2416,17 +2406,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> info.position = display != null ? display.getIndexOf(stack) : 0; info.configuration.setTo(stack.getConfiguration()); - final int numTasks = stack.getChildCount(); + final int numTasks = stack.getDescendantTaskCount(); info.taskIds = new int[numTasks]; info.taskNames = new String[numTasks]; info.taskBounds = new Rect[numTasks]; info.taskUserIds = new int[numTasks]; - final int[] currenIndex = {0}; + final int[] currentIndex = {0}; final PooledConsumer c = PooledLambda.obtainConsumer( RootWindowContainer::processTaskForStackInfo, PooledLambda.__(Task.class), info, - currenIndex); - stack.forAllTasks(c, false); + currentIndex); + stack.forAllTasks(c, false /* traverseTopToBottom */, stack); c.recycle(); final ActivityRecord top = stack.topRunningActivity(); @@ -2569,7 +2559,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } ActivityStack findStackBehind(ActivityStack stack) { - final DisplayContent display = getDisplayContent(stack.getDisplayId()); + final DisplayContent display = stack.getDisplayContent(); if (display != null) { for (int i = display.getStackCount() - 1; i >= 0; i--) { if (display.getStackAt(i) == stack && i > 0) { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 98127ab2eec8..6ebbf776feba 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -93,6 +93,9 @@ class RunningTasks { } private void processTask(Task task) { + if (task.isRootTask()) { + return; + } if (task.getTopNonFinishingActivity() == null) { // Skip if there are no activities in the task return; diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 399c5d3ae45f..bfb69172e9eb 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -16,8 +16,12 @@ package com.android.server.wm; +import static com.android.server.wm.AnimationSpecProto.ROTATE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; +import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA; +import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA; import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -25,7 +29,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; +import android.animation.ArgbEvaluator; import android.content.Context; +import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -40,7 +46,9 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; +import com.android.internal.R; import com.android.server.protolog.common.ProtoLog; +import com.android.server.wm.utils.RotationAnimationUtils; import java.io.PrintWriter; @@ -60,10 +68,10 @@ import java.io.PrintWriter; * animation first rotate the new content into the old orientation to then be able to * animate to the new orientation * - * <li> The exiting Blackframe: <p> - * Because the change of orientation might change the width and height of the content (i.e - * when rotating from portrait to landscape) we "crop" the new content using black frames - * around the screenshot so the new content does not go beyond the screenshot's bounds + * <li> The Background color frame: <p> + * To have the animation seem more seamless, we add a color transitioning background behind the + * exiting and entering layouts. We compute the brightness of the start and end + * layouts and transition from the two brightness values as grayscale underneath the animation * * <li> The entering Blackframe: <p> * The enter Blackframe is similar to the exit Blackframe but is only used when a custom @@ -81,8 +89,6 @@ class ScreenRotationAnimation { */ private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER; private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE; - private static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1; - private static final int SCREEN_FREEZE_LAYER_EXIT = SCREEN_FREEZE_LAYER_BASE + 2; private final Context mContext; private final DisplayContent mDisplayContent; @@ -90,16 +96,18 @@ class ScreenRotationAnimation { private final Transformation mRotateExitTransformation = new Transformation(); private final Transformation mRotateEnterTransformation = new Transformation(); // Complete transformations being applied. - private final Transformation mExitTransformation = new Transformation(); private final Transformation mEnterTransformation = new Transformation(); - private final Matrix mFrameInitialMatrix = new Matrix(); private final Matrix mSnapshotInitialMatrix = new Matrix(); - private final Matrix mSnapshotFinalMatrix = new Matrix(); - private final Matrix mExitFrameFinalMatrix = new Matrix(); private final WindowManagerService mService; + /** Only used for custom animations and not screen rotation. */ private SurfaceControl mEnterBlackFrameLayer; - private SurfaceControl mRotationLayer; - private SurfaceControl mSurfaceControl; + /** This layer contains the actual screenshot that is to be faded out. */ + private SurfaceControl mScreenshotLayer; + /** + * Only used for screen rotation and not custom animations. Layered behind all other layers + * to avoid showing any "empty" spots + */ + private SurfaceControl mBackColorSurface; private BlackFrame mEnteringBlackFrame; private int mWidth, mHeight; @@ -120,8 +128,11 @@ class ScreenRotationAnimation { private boolean mFinishAnimReady; private long mFinishAnimStartTime; private boolean mForceDefaultOrientation; - private BlackFrame mExitingBlackFrame; private SurfaceRotationAnimationController mSurfaceRotationAnimationController; + /** Intensity of light/whiteness of the layout before rotation occurs. */ + private float mStartLuma; + /** Intensity of light/whiteness of the layout after rotation occurs. */ + private float mEndLuma; public ScreenRotationAnimation(Context context, DisplayContent displayContent, boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) { @@ -162,9 +173,15 @@ class ScreenRotationAnimation { final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { - mRotationLayer = displayContent.makeOverlay() + mBackColorSurface = displayContent.makeChildSurface(null) + .setName("BackColorSurface") + .setColorLayer() + .build(); + + mScreenshotLayer = displayContent.makeOverlay() .setName("RotationLayer") - .setContainerLayer() + .setBufferSize(mWidth, mHeight) + .setSecure(isSecure) .build(); mEnterBlackFrameLayer = displayContent.makeOverlay() @@ -172,26 +189,21 @@ class ScreenRotationAnimation { .setContainerLayer() .build(); - mSurfaceControl = mService.makeSurfaceBuilder(null) - .setName("ScreenshotSurface") - .setParent(mRotationLayer) - .setBufferSize(mWidth, mHeight) - .setSecure(isSecure) - .build(); - // In case display bounds change, screenshot buffer and surface may mismatch so set a // scaling mode. SurfaceControl.Transaction t2 = mService.mTransactionFactory.get(); - t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW); + t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW); t2.apply(true /* sync */); // Capture a screenshot into the surface we just created. final int displayId = display.getDisplayId(); final Surface surface = mService.mSurfaceFactory.get(); - surface.copyFrom(mSurfaceControl); + surface.copyFrom(mScreenshotLayer); SurfaceControl.ScreenshotGraphicBuffer gb = mService.mDisplayManagerInternal.screenshot(displayId); if (gb != null) { + mStartLuma = RotationAnimationUtils.getAvgBorderLuma(gb.getGraphicBuffer(), + gb.getColorSpace()); try { surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(), gb.getColorSpace()); @@ -202,13 +214,15 @@ class ScreenRotationAnimation { // screenshot surface we display it in also has FLAG_SECURE so that // the user can not screenshot secure layers via the screenshot surface. if (gb.containsSecureLayers()) { - t.setSecure(mSurfaceControl, true); + t.setSecure(mScreenshotLayer, true); } - t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE); - t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT); - t.setAlpha(mSurfaceControl, 0); - t.show(mRotationLayer); - t.show(mSurfaceControl); + t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE); + t.reparent(mBackColorSurface, displayContent.getSurfaceControl()); + t.setLayer(mBackColorSurface, -1); + t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); + t.setAlpha(mBackColorSurface, 1); + t.show(mScreenshotLayer); + t.show(mBackColorSurface); } else { Slog.w(TAG, "Unable to take screenshot of display " + displayId); } @@ -218,32 +232,11 @@ class ScreenRotationAnimation { } ProtoLog.i(WM_SHOW_SURFACE_ALLOC, - " FREEZE %s: CREATE", mSurfaceControl); + " FREEZE %s: CREATE", mScreenshotLayer); setRotation(t, originalRotation); t.apply(); } - private static void createRotationMatrix(int rotation, int width, int height, - Matrix outMatrix) { - switch (rotation) { - case Surface.ROTATION_0: - outMatrix.reset(); - break; - case Surface.ROTATION_90: - outMatrix.setRotate(90, 0, 0); - outMatrix.postTranslate(height, 0); - break; - case Surface.ROTATION_180: - outMatrix.setRotate(180, 0, 0); - outMatrix.postTranslate(width, height); - break; - case Surface.ROTATION_270: - outMatrix.setRotate(270, 0, 0); - outMatrix.postTranslate(0, width); - break; - } - } - public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(STARTED, mStarted); @@ -252,11 +245,11 @@ class ScreenRotationAnimation { } boolean hasScreenshot() { - return mSurfaceControl != null; + return mScreenshotLayer != null; } private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) { - if (mRotationLayer == null) { + if (mScreenshotLayer == null) { return; } matrix.getValues(mTmpFloats); @@ -267,24 +260,19 @@ class ScreenRotationAnimation { x -= mCurrentDisplayRect.left; y -= mCurrentDisplayRect.top; } - t.setPosition(mRotationLayer, x, y); - t.setMatrix(mRotationLayer, + t.setPosition(mScreenshotLayer, x, y); + t.setMatrix(mScreenshotLayer, mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); - t.setAlpha(mSurfaceControl, (float) 1.0); - t.setAlpha(mRotationLayer, (float) 1.0); - t.show(mRotationLayer); + t.setAlpha(mScreenshotLayer, (float) 1.0); + t.show(mScreenshotLayer); } public void printTo(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl); + pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer); pw.print(" mWidth="); pw.print(mWidth); pw.print(" mHeight="); pw.println(mHeight); - pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame); - if (mExitingBlackFrame != null) { - mExitingBlackFrame.printTo(prefix + " ", pw); - } pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame); @@ -303,20 +291,10 @@ class ScreenRotationAnimation { pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation); pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println(); - pw.print(prefix); pw.print("mExitTransformation="); - mExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mEnterTransformation="); mEnterTransformation.printShortString(pw); pw.println(); - pw.print(prefix); pw.print("mFrameInitialMatrix="); - mFrameInitialMatrix.printShortString(pw); - pw.println(); pw.print(prefix); pw.print("mSnapshotInitialMatrix="); - mSnapshotInitialMatrix.printShortString(pw); - pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print("mExitFrameFinalMatrix="); - mExitFrameFinalMatrix.printShortString(pw); - pw.println(); + mSnapshotInitialMatrix.printShortString(pw);pw.println(); pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation); if (mForceDefaultOrientation) { pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString()); @@ -331,7 +309,7 @@ class ScreenRotationAnimation { // to the snapshot to make it stay in the same original position // with the current screen rotation. int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0); - createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); + RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); setRotationTransform(t, mSnapshotInitialMatrix); } @@ -341,7 +319,7 @@ class ScreenRotationAnimation { */ private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mSurfaceControl == null) { + if (mScreenshotLayer == null) { // Can't do animation. return false; } @@ -354,89 +332,58 @@ class ScreenRotationAnimation { // Figure out how the screen has moved from the original rotation. int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation); - mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_alpha); final boolean customAnim; if (exitAnim != 0 && enterAnim != 0) { customAnim = true; mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim); + mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, + R.anim.screen_rotate_alpha); } else { customAnim = false; - switch (delta) { + switch (delta) { /* Counter-Clockwise Rotations */ case Surface.ROTATION_0: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_0_exit); + R.anim.screen_rotate_0_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_0_enter); + R.anim.screen_rotate_0_enter); break; case Surface.ROTATION_90: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_plus_90_exit); + R.anim.screen_rotate_plus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_plus_90_enter); + R.anim.screen_rotate_plus_90_enter); break; case Surface.ROTATION_180: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_180_exit); + R.anim.screen_rotate_180_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_180_enter); + R.anim.screen_rotate_180_enter); break; case Surface.ROTATION_270: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_minus_90_exit); + R.anim.screen_rotate_minus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_minus_90_enter); + R.anim.screen_rotate_minus_90_enter); break; } } - // Initialize the animations. This is a hack, redefining what "parent" - // means to allow supplying the last and next size. In this definition - // "%p" is the original (let's call it "previous") size, and "%" is the - // screen's current/new size. - mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); - mAnimRunning = false; - mFinishAnimReady = false; - mFinishAnimStartTime = -1; - mRotateExitAnimation.restrictDuration(maxAnimationDuration); mRotateExitAnimation.scaleCurrentDuration(animationScale); + mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateEnterAnimation.restrictDuration(maxAnimationDuration); mRotateEnterAnimation.scaleCurrentDuration(animationScale); - mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); - mRotateAlphaAnimation.scaleCurrentDuration(animationScale); - if (!customAnim && mExitingBlackFrame == null) { - try { - // Compute the transformation matrix that must be applied - // the the black frame to make it stay in the initial position - // before the new screen rotation. This is different than the - // snapshot transformation because the snapshot is always based - // of the native orientation of the screen, not the orientation - // we were last in. - createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix); - - final Rect outer; - final Rect inner; - if (mForceDefaultOrientation) { - // Going from a smaller Display to a larger Display, add curtains to sides - // or top and bottom. Going from a larger to smaller display will result in - // no BlackSurfaces being constructed. - outer = mCurrentDisplayRect; - inner = mOriginalDisplayRect; - } else { - outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2); - inner = new Rect(0, 0, mWidth, mHeight); - } - mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner, - SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation, - mRotationLayer); - } catch (OutOfResourcesException e) { - Slog.w(TAG, "Unable to allocate black surface", e); - } + mAnimRunning = false; + mFinishAnimReady = false; + mFinishAnimStartTime = -1; + + if (customAnim) { + mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); + mRotateAlphaAnimation.scaleCurrentDuration(animationScale); } if (customAnim && mEnteringBlackFrame == null) { @@ -451,7 +398,12 @@ class ScreenRotationAnimation { } } - mSurfaceRotationAnimationController.startAnimation(); + if (customAnim) { + mSurfaceRotationAnimationController.startCustomAnimation(); + } else { + mSurfaceRotationAnimationController.startScreenRotationAnimation(); + } + return true; } @@ -460,11 +412,13 @@ class ScreenRotationAnimation { */ public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mSurfaceControl == null) { + if (mScreenshotLayer == null) { // Can't do animation. return false; } if (!mStarted) { + mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(), + mDisplayContent.getWindowingLayer()); startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight, exitAnim, enterAnim); } @@ -480,28 +434,30 @@ class ScreenRotationAnimation { mSurfaceRotationAnimationController.cancel(); mSurfaceRotationAnimationController = null; } - if (mSurfaceControl != null) { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mSurfaceControl); - mSurfaceControl = null; + + if (mScreenshotLayer != null) { + ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer); SurfaceControl.Transaction t = mService.mTransactionFactory.get(); - if (mRotationLayer != null) { - if (mRotationLayer.isValid()) { - t.remove(mRotationLayer); - } - mRotationLayer = null; + if (mScreenshotLayer.isValid()) { + t.remove(mScreenshotLayer); } + mScreenshotLayer = null; + if (mEnterBlackFrameLayer != null) { if (mEnterBlackFrameLayer.isValid()) { t.remove(mEnterBlackFrameLayer); } mEnterBlackFrameLayer = null; } + if (mBackColorSurface != null) { + if (mBackColorSurface.isValid()) { + t.remove(mBackColorSurface); + } + mBackColorSurface = null; + } t.apply(); } - if (mExitingBlackFrame != null) { - mExitingBlackFrame.kill(); - mExitingBlackFrame = null; - } + if (mEnteringBlackFrame != null) { mEnteringBlackFrame.kill(); mEnteringBlackFrame = null; @@ -537,18 +493,28 @@ class ScreenRotationAnimation { * Utility class that runs a {@link ScreenRotationAnimation} on the {@link * SurfaceAnimationRunner}. * <p> - * The rotation animation is divided into the following hierarchy: + * The rotation animation supports both screen rotation and custom animations + * + * For custom animations: + * <ul> + * <li> + * The screenshot layer which has an added animation of it's alpha channel + * ("screen_rotate_alpha") and that will be applied along with the custom animation. + * </li> + * <li> A device layer that is animated with the provided custom animation </li> + * </ul> + * + * For screen rotation: * <ul> - * <li> A first rotation layer, containing the blackframes. This layer is animated by the - * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation. - * <ul> - * <li> A child layer containing the screenshot on which is added an animation of it's - * alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li> - * </ul> - * <li> A second rotation layer used when custom animations are passed in + * <li> A rotation layer that is both rotated and faded out during a single animation </li> + * <li> A device layer that is both rotated and faded in during a single animation </li> + * <li> A background color layer that transitions colors behind the first two layers </li> + * </ul> + * * {@link ScreenRotationAnimation#startAnimation( * SurfaceControl.Transaction, long, float, int, int, int, int)}. * </ul> + * * <p> * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of * this three {@link SurfaceControl}s which then delegates the animation to the @@ -556,22 +522,35 @@ class ScreenRotationAnimation { */ class SurfaceRotationAnimationController { private SurfaceAnimator mDisplayAnimator; - private SurfaceAnimator mEnterBlackFrameAnimator; private SurfaceAnimator mScreenshotRotationAnimator; private SurfaceAnimator mRotateScreenAnimator; + private SurfaceAnimator mEnterBlackFrameAnimator; + + void startCustomAnimation() { + try { + mService.mSurfaceAnimationRunner.deferStartingAnimations(); + mRotateScreenAnimator = startScreenshotAlphaAnimation(); + mDisplayAnimator = startDisplayRotation(); + if (mEnteringBlackFrame != null) { + mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); + } + } finally { + mService.mSurfaceAnimationRunner.continueStartingAnimations(); + } + } /** * Start the rotation animation of the display and the screenshot on the * {@link SurfaceAnimationRunner}. */ - void startAnimation() { - mRotateScreenAnimator = startScreenshotAlphaAnimation(); - mDisplayAnimator = startDisplayRotation(); - if (mExitingBlackFrame != null) { + void startScreenRotationAnimation() { + try { + mService.mSurfaceAnimationRunner.deferStartingAnimations(); + mDisplayAnimator = startDisplayRotation(); mScreenshotRotationAnimator = startScreenshotRotationAnimation(); - } - if (mEnteringBlackFrame != null) { - mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); + startColorAnimation(); + } finally { + mService.mSurfaceAnimationRunner.continueStartingAnimations(); } } @@ -596,8 +575,8 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotAlphaAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mSurfaceControl) - .setAnimationLeashParent(mRotationLayer) + .setSurfaceControl(mScreenshotLayer) + .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) .setWidth(mWidth) .setHeight(mHeight) .build(), @@ -616,13 +595,67 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotRotationAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mRotationLayer) + .setSurfaceControl(mScreenshotLayer) .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) .build(), createWindowAnimationSpec(mRotateExitAnimation), this::onAnimationEnd); } + + /** + * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a + * grayscale color + */ + private void startColorAnimation() { + int colorTransitionMs = mContext.getResources().getInteger( + R.integer.config_screen_rotation_color_transition); + final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner; + final float[] rgbTmpFloat = new float[3]; + final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma); + final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma); + final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale(); + final ArgbEvaluator va = ArgbEvaluator.getInstance(); + runner.startAnimation( + new LocalAnimationAdapter.AnimationSpec() { + @Override + public long getDuration() { + return duration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl leash, + long currentPlayTime) { + float fraction = (float)currentPlayTime / (float)getDuration(); + int color = (Integer) va.evaluate(fraction, startColor, endColor); + Color middleColor = Color.valueOf(color); + rgbTmpFloat[0] = middleColor.red(); + rgbTmpFloat[1] = middleColor.green(); + rgbTmpFloat[2] = middleColor.blue(); + if (leash.isValid()) { + t.setColor(leash, rgbTmpFloat); + } + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "startLuma=" + mStartLuma + + " endLuma=" + mEndLuma + + " durationMs=" + colorTransitionMs); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(ROTATE); + proto.write(START_LUMA, mStartLuma); + proto.write(END_LUMA, mEndLuma); + proto.write(DURATION_MS, colorTransitionMs); + proto.end(token); + } + }, + mBackColorSurface, mDisplayContent.getPendingTransaction(), null); + } + private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) { return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */, false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */); @@ -646,7 +679,6 @@ class ScreenRotationAnimation { LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter( animationSpec, mService.mSurfaceAnimationRunner); - animator.startAnimation(mDisplayContent.getPendingTransaction(), localAnimationAdapter, false); return animator; @@ -692,7 +724,6 @@ class ScreenRotationAnimation { if (mEnterBlackFrameAnimator != null) { mEnterBlackFrameAnimator.cancelAnimation(); } - if (mScreenshotRotationAnimator != null) { mScreenshotRotationAnimator.cancelAnimation(); } @@ -704,6 +735,10 @@ class ScreenRotationAnimation { if (mDisplayAnimator != null) { mDisplayAnimator.cancelAnimation(); } + + if (mBackColorSurface != null) { + mService.mSurfaceAnimationRunner.onAnimationCancelled(mBackColorSurface); + } } public boolean isAnimating() { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 3b349b817108..5babdafc856d 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -31,6 +31,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.Nullable; import android.content.ClipData; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; @@ -189,7 +190,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); @@ -197,7 +199,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrame, outContentInsets, outVisibleInsets, outStableInsets, outBackdropFrame, cutout, - mergedConfiguration, outSurfaceControl, outInsetsState); + mergedConfiguration, outSurfaceControl, outInsetsState, outSurfaceSize); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 50cea2ed52cc..5633b6be87b9 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -78,6 +78,10 @@ class SurfaceAnimationRunner { @GuardedBy("mLock") private boolean mAnimationStartDeferred; + /** + * There should only ever be one instance of this class. Usual spot for it is with + * {@link WindowManagerService} + */ SurfaceAnimationRunner(Supplier<Transaction> transactionFactory, PowerManagerInternal powerManagerInternal) { this(null /* callbackProvider */, null /* animatorFactory */, diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index b7201b4dbde5..9e6cb6884820 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -30,6 +30,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; @@ -69,6 +71,7 @@ import static com.android.server.am.TaskRecordProto.RESIZE_MODE; import static com.android.server.am.TaskRecordProto.STACK_ID; import static com.android.server.am.TaskRecordProto.TASK; import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN; +import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; @@ -82,6 +85,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECEN import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS; @@ -93,6 +97,7 @@ import static com.android.server.wm.TaskProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; @@ -202,9 +207,9 @@ class Task extends WindowContainer<WindowContainer> { // Current version of the task record we persist. Used to check if we need to run any upgrade // code. - private static final int PERSIST_TASK_VERSION = 1; + static final int PERSIST_TASK_VERSION = 1; - private static final int INVALID_MIN_SIZE = -1; + static final int INVALID_MIN_SIZE = -1; /** * The modes to control how the stack is moved to the front when calling {@link Task#reparent}. @@ -331,6 +336,8 @@ class Task extends WindowContainer<WindowContainer> { final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport(); final ActivityTaskManagerService mAtmService; + final ActivityStackSupervisor mStackSupervisor; + final RootWindowContainer mRootWindowContainer; /* Unique identifier for this task. */ final int mTaskId; @@ -345,8 +352,12 @@ class Task extends WindowContainer<WindowContainer> { // TODO(b/119687367): This member is temporary. private final Rect mOverrideDisplayedBounds = new Rect(); + // Id of the previous display the stack was on. + int mPrevDisplayId = INVALID_DISPLAY; + /** ID of the display which rotation {@link #mRotation} has. */ private int mLastRotationDisplayId = INVALID_DISPLAY; + /** * Display rotation as of the last time {@link #setBounds(Rect)} was called or this task was * moved to a new display. @@ -388,8 +399,37 @@ class Task extends WindowContainer<WindowContainer> { private static Exception sTmpException; + /** ActivityRecords that are exiting, but still on screen for animations. */ + final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>(); + + /** + * When we are in the process of pausing an activity, before starting the + * next one, this variable holds the activity that is currently being paused. + */ + ActivityRecord mPausingActivity = null; + + /** + * This is the last activity that we put into the paused state. This is + * used to determine if we need to do an activity transition while sleeping, + * when we normally hold the top activity paused. + */ + ActivityRecord mLastPausedActivity = null; + + /** + * Activities that specify No History must be removed once the user navigates away from them. + * If the device goes to sleep with such an activity in the paused state then we save it here + * and finish it later if another activity replaces it on wakeup. + */ + ActivityRecord mLastNoHistoryActivity = null; + + /** Current activity that is resumed, or null if there is none. */ + ActivityRecord mResumedActivity = null; + private boolean mForceShowForAllUsers; + /** When set, will force the task to report as invisible. */ + boolean mForceHidden = false; + private final FindRootHelper mFindRootHelper = new FindRootHelper(); private class FindRootHelper { private ActivityRecord mRoot; @@ -488,6 +528,8 @@ class Task extends WindowContainer<WindowContainer> { EventLogTags.writeWmTaskCreated(_taskId, stack != null ? stack.mStackId : INVALID_STACK_ID); mAtmService = atmService; + mStackSupervisor = atmService.mStackSupervisor; + mRootWindowContainer = mAtmService.mRootWindowContainer; mTaskId = _taskId; mUserId = _userId; mResizeMode = resizeMode; @@ -551,7 +593,7 @@ class Task extends WindowContainer<WindowContainer> { if (autoRemoveFromRecents() || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. - mAtmService.mStackSupervisor.mRecentTasks.remove(this); + mStackSupervisor.mRecentTasks.remove(this); } removeIfPossible(); @@ -560,13 +602,18 @@ class Task extends WindowContainer<WindowContainer> { @VisibleForTesting @Override void removeIfPossible() { - mAtmService.getLockTaskController().clearLockedTask(this); + final boolean isRootTask = isRootTask(); + if (!isRootTask) { + mAtmService.getLockTaskController().clearLockedTask(this); + } if (shouldDeferRemoval()) { if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId); return; } removeImmediately(); - mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId); + if (!isRootTask) { + mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId); + } } void setResizeMode(int resizeMode) { @@ -574,8 +621,8 @@ class Task extends WindowContainer<WindowContainer> { return; } mResizeMode = resizeMode; - mAtmService.mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities(); + mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); + mRootWindowContainer.resumeFocusedStacksTopActivities(); updateTaskDescription(); } @@ -592,7 +639,7 @@ class Task extends WindowContainer<WindowContainer> { setBounds(bounds); if (!inFreeformWindowingMode()) { // re-restore the task so it can have the proper stack association. - mAtmService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP); + mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP); } return true; } @@ -628,10 +675,9 @@ class Task extends WindowContainer<WindowContainer> { // this won't cause tons of irrelevant windows being preserved because only // activities in this task may experience a bounds change. Configs for other // activities stay the same. - mAtmService.mRootWindowContainer.ensureActivitiesVisible(r, 0, - preserveWindow); + mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow); if (!kept) { - mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities(); + mRootWindowContainer.resumeFocusedStacksTopActivities(); } } } @@ -695,8 +741,8 @@ class Task extends WindowContainer<WindowContainer> { boolean reparent(ActivityStack preferredStack, int position, @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange, String reason) { - final ActivityStackSupervisor supervisor = mAtmService.mStackSupervisor; - final RootWindowContainer root = mAtmService.mRootWindowContainer; + final ActivityStackSupervisor supervisor = mStackSupervisor; + final RootWindowContainer root = mRootWindowContainer; final WindowManagerService windowManager = mAtmService.mWindowManager; final ActivityStack sourceStack = getStack(); final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack, @@ -765,7 +811,7 @@ class Task extends WindowContainer<WindowContainer> { wasPaused, reason); } if (!animate) { - mAtmService.mStackSupervisor.mNoAnimActivities.add(topActivity); + mStackSupervisor.mNoAnimActivities.add(topActivity); } // We might trigger a configuration change. Save the current task bounds for freezing. @@ -784,7 +830,7 @@ class Task extends WindowContainer<WindowContainer> { } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) { Rect bounds = getLaunchBounds(); if (bounds == null) { - mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null); + mStackSupervisor.getLaunchParamsController().layoutTask(this, null); bounds = configBounds; } kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume); @@ -792,7 +838,7 @@ class Task extends WindowContainer<WindowContainer> { if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) { // Move recents to front so it is not behind home stack when going into docked // mode - mAtmService.mStackSupervisor.moveRecentsStackToFront(reason); + mStackSupervisor.moveRecentsStackToFront(reason); } kept = resize(toStack.getRequestedOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow, deferResume); @@ -858,6 +904,14 @@ class Task extends WindowContainer<WindowContainer> { mCallingPackage = r.launchedFromPackage; setIntent(r.intent, r.info); setLockTaskAuth(r); + + final WindowContainer parent = getParent(); + if (parent != null) { + final Task t = parent.asTask(); + if (t != null) { + t.setIntent(r); + } + } } /** Sets the original intent, _without_ updating the calling uid or package. */ @@ -971,8 +1025,13 @@ class Task extends WindowContainer<WindowContainer> { } boolean returnsToHomeStack() { - final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; - return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + if (inMultiWindowMode() || !hasChild()) return false; + if (intent != null) { + final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; + return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + } + final Task bottomTask = getBottomMostTask(); + return bottomTask != this && bottomTask.returnsToHomeStack(); } void setPrevAffiliate(Task prevAffiliate) { @@ -987,37 +1046,72 @@ class Task extends WindowContainer<WindowContainer> { @Override void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { - final ActivityStack oldStack = ((ActivityStack) oldParent); - final ActivityStack newStack = ((ActivityStack) newParent); + final DisplayContent display = newParent != null + ? ((WindowContainer) newParent).getDisplayContent() : null; + final DisplayContent oldDisplay = oldParent != null + ? ((WindowContainer) oldParent).getDisplayContent() : null; + + mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY; // Task is going to be removed, clean it up before detaching from hierarchy. if (oldParent != null && newParent == null) { cleanUpResourcesForDestroy(); } + if (display != null) { + // TODO(NOW!): Chat with the erosky@ of this code to see if this really makes sense here... + // Rotations are relative to the display. This means if there are 2 displays rotated + // differently (eg. 2 monitors with one landscape and one portrait), moving a stack + // from one to the other could look like a rotation change. To prevent this + // apparent rotation change (and corresponding bounds rotation), pretend like our + // current rotation is already the same as the new display. + // Note, if ActivityStack or related logic ever gets nested, this logic will need + // to move to onConfigurationChanged. + getConfiguration().windowConfiguration.setRotation( + display.getWindowConfiguration().getRotation()); + } + super.onParentChanged(newParent, oldParent); - if (oldStack != null) { - final PooledConsumer c = PooledLambda.obtainConsumer( - ActivityStack::onActivityRemovedFromStack, oldStack, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(c); - c.recycle(); + // TODO(NOW): The check for null display content and setting it to null doesn't really + // make sense here... + + // TODO(stack-merge): This is mostly taking care of the case where the stask is removing from + // the display, so we should probably consolidate it there instead. + + if (getParent() == null && mDisplayContent != null) { + EventLogTags.writeWmStackRemoved(getStackId()); + mDisplayContent = null; + mWmService.mWindowPlacerLocked.requestTraversal(); + } - if (oldStack.inPinnedWindowingMode() - && (newStack == null || !newStack.inPinnedWindowingMode())) { + if (oldParent != null) { + final Task oldParentTask = ((WindowContainer) oldParent).asTask(); + if (oldParentTask != null) { + final PooledConsumer c = PooledLambda.obtainConsumer( + Task::cleanUpActivityReferences, oldParentTask, + PooledLambda.__(ActivityRecord.class)); + forAllActivities(c); + c.recycle(); + } + + if (oldParent.inPinnedWindowingMode() + && (newParent == null || !newParent.inPinnedWindowingMode())) { // Notify if a task from the pinned stack is being removed // (or moved depending on the mode). mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned(); } } - if (newStack != null) { - final PooledConsumer c = PooledLambda.obtainConsumer( - ActivityStack::onActivityAddedToStack, newStack, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(c); - c.recycle(); + if (newParent != null) { + final Task newParentTask = ((WindowContainer) newParent).asTask(); + if (newParentTask != null) { + final ActivityRecord top = newParentTask.getTopNonFinishingActivity( + false /* includeOverlays */); + if (top != null && top.isState(RESUMED)) { + newParentTask.setResumedActivity(top, "addedToTask"); + } + } // TODO: Ensure that this is actually necessary here // Notify the voice session if required @@ -1048,7 +1142,41 @@ class Task extends WindowContainer<WindowContainer> { forceWindowsScaleable(false /* force */); } - mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay(); + mRootWindowContainer.updateUIDsPresentOnDisplay(); + } + + void cleanUpActivityReferences(ActivityRecord r) { + final WindowContainer parent = getParent(); + if (parent != null && parent.asTask() != null) { + parent.asTask().cleanUpActivityReferences(r); + return; + } + r.removeTimeouts(); + mExitingActivities.remove(r); + + if (mResumedActivity != null && mResumedActivity == r) { + setResumedActivity(null, "cleanUpActivityReferences"); + } + if (mPausingActivity != null && mPausingActivity == r) { + mPausingActivity = null; + } + } + + /** @return the currently resumed activity. */ + ActivityRecord getResumedActivity() { + return mResumedActivity; + } + + void setResumedActivity(ActivityRecord r, String reason) { + if (mResumedActivity == r) { + return; + } + + if (ActivityTaskManagerDebugConfig.DEBUG_STACK) Slog.d(TAG_STACK, + "setResumedActivity stack:" + this + " + from: " + + mResumedActivity + " to:" + r + " reason:" + reason); + mResumedActivity = r; + mStackSupervisor.updateTopResumedActivityIfNeeded(); } void updateTaskMovement(boolean toFront) { @@ -1061,7 +1189,7 @@ class Task extends WindowContainer<WindowContainer> { mLastTimeMoved *= -1; } } - mAtmService.mRootWindowContainer.invalidateTaskLayers(); + mRootWindowContainer.invalidateTaskLayers(); } // Close up recents linked list. @@ -1114,7 +1242,11 @@ class Task extends WindowContainer<WindowContainer> { /** Returns the intent for the root activity for this task */ Intent getBaseIntent() { - return intent != null ? intent : affinityIntent; + if (intent != null) return intent; + if (affinityIntent != null) return affinityIntent; + // Probably a task that contains other tasks, so return the intent for the top task? + final Task topTask = getTopMostTask(); + return topTask != null ? topTask.getBaseIntent() : null; } /** Returns the first non-finishing activity from the bottom. */ @@ -1199,11 +1331,18 @@ class Task extends WindowContainer<WindowContainer> { // If this task had any child before we added this one. boolean hadChild = hasChild(); - final ActivityRecord r = (ActivityRecord) child; - index = getAdjustedAddPosition(r, index); - super.addChild(r, index); + index = getAdjustedChildPosition(child, index); + super.addChild(child, index); ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addChild: %s at top.", this); + + // Make sure the list of display UID whitelists is updated + // now that this record is in a new task. + mRootWindowContainer.updateUIDsPresentOnDisplay(); + + final ActivityRecord r = child.asActivityRecord(); + if (r == null) return; + r.inHistory = true; // Only set this based on the first activity @@ -1228,10 +1367,6 @@ class Task extends WindowContainer<WindowContainer> { } updateEffectiveIntent(); - - // Make sure the list of display UID whitelists is updated - // now that this record is in a new task. - mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay(); } void addChild(ActivityRecord r) { @@ -1239,12 +1374,19 @@ class Task extends WindowContainer<WindowContainer> { } @Override - void removeChild(WindowContainer r) { + void removeChild(WindowContainer child) { + removeChild(child, "removeChild"); + } + + void removeChild(WindowContainer r, String reason) { if (!mChildren.contains(r)) { Slog.e(TAG, "removeChild: r=" + r + " not found in t=" + this); return; } + if (DEBUG_TASK_MOVEMENT) { + Slog.d(TAG_WM, "removeChild: child=" + r + " reason=" + reason); + } super.removeChild(r); if (inPinnedWindowingMode()) { @@ -1254,7 +1396,15 @@ class Task extends WindowContainer<WindowContainer> { mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); } - final String reason = "removeChild"; + final boolean isRootTask = isRootTask(); + if (isRootTask) { + final DisplayContent display = getDisplayContent(); + if (display.isSingleTaskInstance()) { + mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId); + } + display.mDisplayContent.setLayoutNeeded(); + } + if (hasChild()) { updateEffectiveIntent(); @@ -1269,12 +1419,14 @@ class Task extends WindowContainer<WindowContainer> { // work. // TODO: If the callers to removeChild() changes such that we have multiple places // where we are destroying the task, move this back into removeChild() - mAtmService.mStackSupervisor.removeTask(this, false /* killProcess */, + mStackSupervisor.removeTask(this, false /* killProcess */, !REMOVE_FROM_RECENTS, reason); } } else if (!mReuseTask) { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse - getStack().removeChild(this, reason); + if (!isRootTask) { + getStack().removeChild(this, reason); + } EventLogTags.writeWmTaskRemoved(mTaskId, "removeChild: last r=" + r + " in t=" + this); removeIfPossible(); @@ -1445,11 +1597,15 @@ class Task extends WindowContainer<WindowContainer> { @Override public boolean supportsSplitScreenWindowingMode() { + final Task topTask = getTopMostTask(); + return super.supportsSplitScreenWindowingMode() + && (topTask == null || topTask.supportsSplitScreenWindowingModeInner()); + } + + private boolean supportsSplitScreenWindowingModeInner() { // A task can not be docked even if it is considered resizeable because it only supports // picture-in-picture mode but has a non-resizeable resizeMode return super.supportsSplitScreenWindowingMode() - // TODO(task-group): Probably makes sense to move this and associated code into - // WindowContainer so it affects every node. && mAtmService.mSupportsSplitScreenMultiWindow && (mAtmService.mForceResizableActivities || (isResizeable(false /* checkSupportsPip */) @@ -1464,7 +1620,7 @@ class Task extends WindowContainer<WindowContainer> { * secondary display. */ boolean canBeLaunchedOnDisplay(int displayId) { - return mAtmService.mStackSupervisor.canPlaceEntityOnDisplay(displayId, + return mStackSupervisor.canPlaceEntityOnDisplay(displayId, -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */); } @@ -1534,6 +1690,14 @@ class Task extends WindowContainer<WindowContainer> { } mAtmService.getTaskChangeNotificationController().notifyTaskDescriptionChanged( getTaskInfo()); + + final WindowContainer parent = getParent(); + if (parent != null) { + final Task t = parent.asTask(); + if (t != null) { + t.updateTaskDescription(); + } + } } private static boolean setTaskDescriptionFromActivityAboveRoot( @@ -1576,9 +1740,11 @@ class Task extends WindowContainer<WindowContainer> { @VisibleForTesting void updateEffectiveIntent() { final ActivityRecord root = getRootActivity(true /*setToBottomIfNone*/); - setIntent(root); - // Update the task description when the activities change - updateTaskDescription(); + if (root != null) { + setIntent(root); + // Update the task description when the activities change + updateTaskDescription(); + } } void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { @@ -1592,9 +1758,8 @@ class Task extends WindowContainer<WindowContainer> { // If the task has no requested minimal size, we'd like to enforce a minimal size // so that the user can not render the task too small to manipulate. We don't need // to do this for the pinned stack as the bounds are controlled by the system. - if (!inPinnedWindowingMode() && getDisplayContent() != null) { - final int defaultMinSizeDp = - mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; + if (!inPinnedWindowingMode() && getStack() != null) { + final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; final DisplayContent display = getDisplayContent(); final float density = (float) display.getConfiguration().densityDpi / DisplayMetrics.DENSITY_DEFAULT; @@ -1658,10 +1823,25 @@ class Task extends WindowContainer<WindowContainer> { * @param reason The reason for the change. */ void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) { - final ActivityStack parent = getStack(); + final Task parentTask = getParent().asTask(); + if (parentTask != null) { + parentTask.onActivityStateChanged(record, state, reason); + return; + } - if (parent != null) { - parent.onActivityStateChanged(record, state, reason); + if (record == mResumedActivity && state != RESUMED) { + setResumedActivity(null, reason + " - onActivityStateChanged"); + } + + if (state == RESUMED) { + if (ActivityTaskManagerDebugConfig.DEBUG_STACK) { + Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" + reason); + } + setResumedActivity(record, reason + " - onActivityStateChanged"); + if (record == mRootWindowContainer.getTopResumedActivity()) { + mAtmService.setResumedActivityUncheckLocked(record, reason); + } + mStackSupervisor.mRecentTasks.add(record.getTask()); } } @@ -1683,7 +1863,7 @@ class Task extends WindowContainer<WindowContainer> { final boolean wasInMultiWindowMode = inMultiWindowMode(); super.onConfigurationChanged(newParentConfig); if (wasInMultiWindowMode != inMultiWindowMode()) { - mAtmService.mStackSupervisor.scheduleUpdateMultiWindowMode(this); + mStackSupervisor.scheduleUpdateMultiWindowMode(this); } // If the configuration supports persistent bounds (eg. Freeform), keep track of the @@ -1724,7 +1904,7 @@ class Task extends WindowContainer<WindowContainer> { } // Saves the new state so that we can launch the activity at the same location. - mAtmService.mStackSupervisor.mLaunchParamsPersister.saveTask(this); + mStackSupervisor.mLaunchParamsPersister.saveTask(this); } /** @@ -1986,6 +2166,10 @@ class Task extends WindowContainer<WindowContainer> { @Override void resolveOverrideConfiguration(Configuration newParentConfig) { + if (isRootTask()) { + super.resolveOverrideConfiguration(newParentConfig); + return; + } mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); super.resolveOverrideConfiguration(newParentConfig); int windowingMode = @@ -2010,12 +2194,12 @@ class Task extends WindowContainer<WindowContainer> { final Rect parentBounds = new Rect(newParentConfig.windowConfiguration.getBounds()); final DisplayContent display = getDisplayContent(); - if (display != null && display.mDisplayContent != null) { + if (display != null) { // If a freeform window moves below system bar, there is no way to move it again // by touch. Because its caption is covered by system bar. So we exclude them // from stack bounds. and then caption will be shown inside stable area. final Rect stableBounds = new Rect(); - display.mDisplayContent.getStableRect(stableBounds); + display.getStableRect(stableBounds); parentBounds.intersect(stableBounds); } @@ -2090,6 +2274,7 @@ class Task extends WindowContainer<WindowContainer> { * input stack. */ void updateOverrideConfigurationForStack(ActivityStack inStack) { final ActivityStack stack = getStack(); + if (stack != null && stack == inStack) { return; } @@ -2105,7 +2290,7 @@ class Task extends WindowContainer<WindowContainer> { if (mLastNonFullscreenBounds != null) { setBounds(mLastNonFullscreenBounds); } else { - mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null); + mStackSupervisor.getLaunchParamsController().layoutTask(this, null); } } else { setBounds(inStack.getRequestedOverrideBounds()); @@ -2148,7 +2333,10 @@ class Task extends WindowContainer<WindowContainer> { @Override DisplayContent getDisplayContent() { - return getStack() != null ? getStack().getDisplayContent() : null; + // TODO: Why aren't we just using our own display content vs. parent's??? + final ActivityStack stack = getStack(); + return stack != null && stack != this + ? stack.getDisplayContent() : super.getDisplayContent(); } int getDisplayId() { @@ -2157,7 +2345,8 @@ class Task extends WindowContainer<WindowContainer> { } ActivityStack getStack() { - return (ActivityStack) getParent(); + final WindowContainer parent = getParent(); + return (ActivityStack) (parent instanceof ActivityStack ? parent : this); } /** @@ -2168,27 +2357,99 @@ class Task extends WindowContainer<WindowContainer> { return stack != null ? stack.mStackId : INVALID_STACK_ID; } - // TODO(task-hierarchy): Needs to take a generic WindowManager when task contains other tasks. - int getAdjustedAddPosition(ActivityRecord r, int suggestedPosition) { - int maxPosition = mChildren.size(); - if (!r.isTaskOverlay()) { - // We want to place all non-overlay activities below overlays. - final ActivityRecord bottomMostOverlay = getActivity((ar) -> ar.isTaskOverlay(), false); - if (bottomMostOverlay != null) { - maxPosition = Math.max(mChildren.indexOf(bottomMostOverlay) - 1, 0); + int getDescendantTaskCount() { + final int[] currentCount = {0}; + final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, + PooledLambda.__(Task.class), currentCount); + forAllTasks(c, false /* traverseTopToBottom */, this); + c.recycle(); + return currentCount[0]; + } + + /** Calculate the minimum possible position for a task that can be shown to the user. + * The minimum position will be above all other tasks that can't be shown. + * @param minPosition The minimum position the caller is suggesting. + * We will start adjusting up from here. + * @param size The size of the current task list. + */ + // TODO: Move user to their own window container. + private int computeMinUserPosition(int minPosition, int size) { + while (minPosition < size) { + final WindowContainer child = mChildren.get(minPosition); + final boolean canShow = child.showToCurrentUser(); + if (canShow) { + break; + } + minPosition++; + } + return minPosition; + } + + /** Calculate the maximum possible position for a task that can't be shown to the user. + * The maximum position will be below all other tasks that can be shown. + * @param maxPosition The maximum position the caller is suggesting. + * We will start adjusting down from here. + */ + // TODO: Move user to their own window container. + private int computeMaxUserPosition(int maxPosition) { + while (maxPosition > 0) { + final WindowContainer child = mChildren.get(maxPosition); + final boolean canShow = child.showToCurrentUser(); + if (!canShow) { + break; + } + maxPosition--; + } + return maxPosition; + } + + private int getAdjustedChildPosition(WindowContainer wc, int suggestedPosition) { + final boolean canShowChild = wc.showToCurrentUser(); + + final int size = mChildren.size(); + + // Figure-out min/max possible position depending on if child can show for current user. + int minPosition = (canShowChild) ? computeMinUserPosition(0, size) : 0; + int maxPosition = (canShowChild) ? size : computeMaxUserPosition(size - 1); + + // Factor in always-on-top children in max possible position. + if (!wc.isAlwaysOnTop()) { + + // We want to place all non-always-on-top containers below always-on-top ones. + while (maxPosition > minPosition) { + if (!mChildren.get(maxPosition - 1).isAlwaysOnTop()) break; + --maxPosition; } } - return Math.min(maxPosition, suggestedPosition); + // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid. + if (suggestedPosition == POSITION_BOTTOM && minPosition == 0) { + return POSITION_BOTTOM; + } else if (suggestedPosition == POSITION_TOP && maxPosition == (size - 1)) { + return POSITION_TOP; + } + // Reset position based on minimum/maximum possible positions. + return Math.min(Math.max(suggestedPosition, minPosition), maxPosition); } @Override void positionChildAt(int position, WindowContainer child, boolean includingParents) { - position = getAdjustedAddPosition((ActivityRecord) child, position); + position = getAdjustedChildPosition(child, position); super.positionChildAt(position, child, includingParents); + + // Log positioning. + if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "positionChildAt: child=" + child + + " position=" + position + " parent=" + this); + + final int toTop = position >= (mChildren.size() - 1) ? 1 : 0; + final Task task = child.asTask(); + if (task != null) { + EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, position); + } } - private boolean hasWindowsAlive() { + @VisibleForTesting + boolean hasWindowsAlive() { return getActivity(ActivityRecord::hasWindowsAlive) != null; } @@ -2218,8 +2479,6 @@ class Task extends WindowContainer<WindowContainer> { + " from stack=" + getStack()); EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason); - position = stack.findPositionForTask(this, position); - reparent(stack, position); stack.positionChildAt(position, this, moveParents); @@ -2285,11 +2544,16 @@ class Task extends WindowContainer<WindowContainer> { @Override void onDisplayChanged(DisplayContent dc) { - adjustBoundsForDisplayChangeIfNeeded(dc); + final boolean isRootTask = isRootTask(); + if (!isRootTask) { + adjustBoundsForDisplayChangeIfNeeded(dc); + } super.onDisplayChanged(dc); - final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; - mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged( - mTaskId, displayId); + if (!isRootTask) { + final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; + mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged( + mTaskId, displayId); + } } /** @@ -2431,7 +2695,7 @@ class Task extends WindowContainer<WindowContainer> { } /** Bounds of the task to be used for dimming, as well as touch related tests. */ - public void getDimBounds(Rect out) { + void getDimBounds(Rect out) { final DisplayContent displayContent = getStack().getDisplayContent(); // It doesn't matter if we in particular are part of the resize, since we couldn't have // a DimLayer anyway if we weren't visible. @@ -2564,6 +2828,11 @@ class Task extends WindowContainer<WindowContainer> { mForceShowForAllUsers = forceShowForAllUsers; } + public boolean isAttached() { + final DisplayContent display = getDisplayContent(); + return display != null && !display.isRemoved(); + } + /** * When we are in a floating stack (Freeform, Pinned, ...) we calculate * insets differently. However if we are animating to the fullscreen stack @@ -2575,6 +2844,45 @@ class Task extends WindowContainer<WindowContainer> { && !getStack().isAnimatingBoundsToFullscreen() && !mPreserveNonFloatingState; } + /** + * Returns true if the stack is translucent and can have other contents visible behind it if + * needed. A stack is considered translucent if it don't contain a visible or + * starting (about to be visible) activity that is fullscreen (opaque). + * @param starting The currently starting activity or null if there is none. + */ + @VisibleForTesting + boolean isTranslucent(ActivityRecord starting) { + if (!isAttached() || mForceHidden) { + return true; + } + final PooledPredicate p = PooledLambda.obtainPredicate(Task::isOpaqueActivity, + PooledLambda.__(ActivityRecord.class), starting); + final ActivityRecord opaque = getActivity(p); + p.recycle(); + return opaque == null; + } + + private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) { + if (r.finishing) { + // We don't factor in finishing activities when determining translucency since + // they will be gone soon. + return false; + } + + if (!r.visibleIgnoringKeyguard && r != starting) { + // Also ignore invisible activities that are not the currently starting + // activity (about to be visible). + return false; + } + + if (r.occludesParent() || r.hasWallpaper) { + // Stack isn't translucent if it has at least one fullscreen activity + // that is visible. + return true; + } + return false; + } + @Override public SurfaceControl getAnimationLeashParent() { if (WindowManagerService.sHierarchicalAnimations) { @@ -2626,7 +2934,7 @@ class Task extends WindowContainer<WindowContainer> { return true; } } - return false; + return forAllTasks((t) -> { return t != this && t.isTaskAnimating(); }); } /** @@ -2702,26 +3010,45 @@ class Task extends WindowContainer<WindowContainer> { return mTaskDescription; } + // TODO(task-merge): Figure out what's the right thing to do for places that used it. + boolean isRootTask() { + return getParent() == null || getParent().asTask() == null; + } + @Override boolean fillsParent() { - return matchParentBounds() || !getWindowConfiguration().canResizeTask(); + return matchParentBounds(); + } + + void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) { + if (traverseTopToBottom) { + super.forAllTasks(callback, traverseTopToBottom); + if (excludedTask != this) { + callback.accept(this); + } + } else { + super.forAllTasks(callback, traverseTopToBottom); + if (excludedTask != this) { + callback.accept(this); + } + } } @Override void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) { - // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks. - callback.accept(this); + forAllTasks(callback, traverseTopToBottom, null /* excludedTask */); } @Override boolean forAllTasks(Function<Task, Boolean> callback) { + if (super.forAllTasks(callback)) return true; return callback.apply(this); } @Override Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) { - // I'm a task! - // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks. + final Task t = super.getTask(callback, traverseTopToBottom); + if (t != null) return t; return callback.test(this) ? this : null; } @@ -2757,6 +3084,16 @@ class Task extends WindowContainer<WindowContainer> { return mDimmer; } + void dim(float alpha) { + mDimmer.dimAbove(getPendingTransaction(), alpha); + scheduleAnimation(); + } + + void stopDimming() { + mDimmer.stopDim(getPendingTransaction()); + scheduleAnimation(); + } + boolean isTaskForUser(int userId) { return mUserId == userId; } @@ -2804,7 +3141,7 @@ class Task extends WindowContainer<WindowContainer> { } @Override - public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); final String doublePrefix = prefix + " "; @@ -2864,6 +3201,17 @@ class Task extends WindowContainer<WindowContainer> { return mTaskId == taskId; } + @Override + Task asTask() { + // I'm a task! + return this; + } + + // TODO(task-merge): Figure-out how this should work with hierarchy tasks. + boolean shouldBeVisible(ActivityRecord starting) { + return true; + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(mUserId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); @@ -2950,7 +3298,7 @@ class Task extends WindowContainer<WindowContainer> { if (mRootProcess != null) { pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess); } - pw.print(prefix); pw.print("stackId="); pw.println(getStackId()); + pw.print(prefix); pw.print("taskId=" + mTaskId); pw.println(" stackId=" + getStackId()); pw.print(prefix + "hasBeenVisible=" + hasBeenVisible); pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode)); pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture); @@ -2977,6 +3325,10 @@ class Task extends WindowContainer<WindowContainer> { sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" #"); sb.append(mTaskId); + sb.append(" visible=" + shouldBeVisible(null /* starting */)); + sb.append(" type=" + activityTypeToString(getActivityType())); + sb.append(" mode=" + windowingModeToString(getWindowingMode())); + sb.append(" translucent=" + isTranslucent(null /* starting */)); if (affinity != null) { sb.append(" A="); sb.append(affinity); @@ -2993,8 +3345,7 @@ class Task extends WindowContainer<WindowContainer> { return toString(); } - @Override - public void dumpDebug(ProtoOutputStream proto, long fieldId, + void dumpDebugInner(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { return; @@ -3205,13 +3556,13 @@ class Task extends WindowContainer<WindowContainer> { Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, ActivityStack stack) { - return new Task(service, taskId, info, intent, voiceSession, voiceInteractor, + return new ActivityStack(service, taskId, info, intent, voiceSession, voiceInteractor, null /*taskDescription*/, stack); } Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info, Intent intent, TaskDescription taskDescription, ActivityStack stack) { - return new Task(service, taskId, info, intent, null /*voiceSession*/, + return new ActivityStack(service, taskId, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, taskDescription, stack); } @@ -3228,7 +3579,7 @@ class Task extends WindowContainer<WindowContainer> { int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage, int resizeMode, boolean supportsPictureInPicture, boolean realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, ActivityStack stack) { - return new Task(service, taskId, intent, affinityIntent, affinity, + return new ActivityStack(service, taskId, intent, affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootWasReset, autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription, lastTimeMoved, neverRelinquishIdentity, lastTaskDescription, taskAffiliation, @@ -3470,7 +3821,9 @@ class Task extends WindowContainer<WindowContainer> { } boolean isControlledByTaskOrganizer() { - return mTaskOrganizer != null; + // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally + // we only give control of the root task. + return getTopMostTask().mTaskOrganizer != null; } @Override @@ -3546,13 +3899,16 @@ class Task extends WindowContainer<WindowContainer> { public void setWindowingMode(int windowingMode) { super.setWindowingMode(windowingMode); windowingMode = getWindowingMode(); - /* - * Different windowing modes may be managed by different task organizers. If - * getTaskOrganizer returns null, we still call transferToTaskOrganizer to - * make sure we clear it. - */ - final ITaskOrganizer org = - mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); - setTaskOrganizer(org); + + // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally + // we only give control of the root task. + // Different windowing modes may be managed by different task organizers. If + // getTaskOrganizer returns null, we still call transferToTaskOrganizer to make sure we + // clear it. + if (!isRootTask()) { + final ITaskOrganizer org = + mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + setTaskOrganizer(org); + } } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index e2a21a9e9db4..57de7536409f 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -55,6 +55,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.GraphicBuffer; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; @@ -115,6 +116,7 @@ class TaskSnapshotSurface implements StartingSurface { private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM; private static final int MSG_REPORT_DRAW = 0; private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; + private static final Point sSurfaceSize = new Point(); //tmp var for unused relayout param private final Window mWindow; private final Surface mSurface; private SurfaceControl mSurfaceControl; @@ -227,7 +229,8 @@ class TaskSnapshotSurface implements StartingSurface { try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, - tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState); + tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState, + sSurfaceSize); } catch (RemoteException e) { // Local call. } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index a43f595e4a9f..1c876d9cc02f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2249,4 +2249,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void setSurfaceControl(SurfaceControl sc) { mSurfaceControl = sc; } + + /** Cheap way of doing cast and instanceof. */ + Task asTask() { + return null; + } + + /** Cheap way of doing cast and instanceof. */ + ActivityRecord asActivityRecord() { + return null; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 77d755d6f36d..cd5da08a4780 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2039,7 +2039,8 @@ public class WindowManagerService extends IWindowManager.Stub long frameNumber, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { int result = 0; boolean configChanged; final int pid = Binder.getCallingPid(); @@ -2361,6 +2362,10 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.sendNewConfiguration(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + if (winAnimator.mSurfaceController != null) { + outSurfaceSize.set(winAnimator.mSurfaceController.getWidth(), + winAnimator.mSurfaceController.getHeight()); + } } Binder.restoreCallingIdentity(origId); @@ -2414,8 +2419,8 @@ public class WindowManagerService extends IWindowManager.Stub return focusMayChange; } - private int createSurfaceControl(SurfaceControl outSurfaceControl, int result, WindowState win, - WindowStateAnimator winAnimator) { + private int createSurfaceControl(SurfaceControl outSurfaceControl, + int result, WindowState win, WindowStateAnimator winAnimator) { if (!win.mHasSurface) { result |= RELAYOUT_RES_SURFACE_CHANGED; } @@ -2694,8 +2699,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.getDockedDividerController().checkMinimizeChanged(animate); } - boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) { return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio( aspectRatio); } @@ -2767,7 +2771,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null && mRoot.getTopChild() != displayContent) { - mRoot.positionChildAt(WindowContainer.POSITION_TOP, displayContent, + displayContent.positionDisplayAt(WindowContainer.POSITION_TOP, true /* includingParents */); } } @@ -5994,12 +5998,10 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" apps="); pw.print(mAppsFreezingScreen); final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked(); pw.print(" mRotation="); pw.print(defaultDisplayContent.getRotation()); - pw.print(" mLastWindowForcedOrientation="); - pw.print(defaultDisplayContent.getLastWindowForcedOrientation()); - pw.print(" mLastOrientation="); - pw.println(defaultDisplayContent.getLastOrientation()); - pw.print(" waitingForConfig="); - pw.println(defaultDisplayContent.mWaitingForConfig); + pw.print(" mLastOrientation="); + pw.println(defaultDisplayContent.getLastOrientation()); + pw.print(" waitingForConfig="); + pw.println(defaultDisplayContent.mWaitingForConfig); pw.print(" Animation settings: disabled="); pw.print(mAnimationsDisabled); pw.print(" window="); pw.print(mWindowAnimationScaleSetting); @@ -7694,7 +7696,7 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent displayContent = touchedWindow.getDisplayContent(); if (!displayContent.isOnTop()) { - displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent, + displayContent.positionDisplayAt(WindowContainer.POSITION_TOP, true /* includingParents */); } handleTaskFocusChange(touchedWindow.getTask()); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 2a1e980dfb3d..43cd66dc0616 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; @@ -38,7 +39,9 @@ import android.annotation.CallSuper; import android.os.Debug; import android.os.IBinder; import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; +import com.android.server.policy.WindowManagerPolicy; import com.android.server.protolog.common.ProtoLog; import java.io.PrintWriter; @@ -228,12 +231,6 @@ class WindowToken extends WindowContainer<WindowState> { return false; } - ActivityRecord asActivityRecord() { - // TODO: Not sure if this is the best way to handle this vs. using instanceof and casting. - // I am not an app window token! - return null; - } - @Override void removeImmediately() { if (mDisplayContent != null) { @@ -256,6 +253,27 @@ class WindowToken extends WindowContainer<WindowState> { super.onDisplayChanged(dc); } + @Override + void assignLayer(SurfaceControl.Transaction t, int layer) { + if (windowType == TYPE_DOCK_DIVIDER) { + // See {@link DisplayContent#mSplitScreenDividerAnchor} + super.assignRelativeLayer(t, mDisplayContent.getSplitScreenDividerAnchor(), 1); + } else if (mRoundedCornerOverlay) { + super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); + } else { + super.assignLayer(t, layer); + } + } + + @Override + SurfaceControl.Builder makeSurface() { + final SurfaceControl.Builder builder = super.makeSurface(); + if (mRoundedCornerOverlay) { + builder.setParent(null); + } + return builder; + } + @CallSuper @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @@ -315,4 +333,8 @@ class WindowToken extends WindowContainer<WindowState> { mOwnerCanManageAppTokens); return mOwnerCanManageAppTokens && (layer > navLayer); } + + int getWindowLayerFromType() { + return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens); + } } diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java new file mode 100644 index 000000000000..0c09c8828682 --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.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.server.wm.utils; + +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceControl; + + +/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/ +public class RotationAnimationUtils { + + /** + * Converts the provided {@link GraphicBuffer} and converts it to a bitmap to then sample the + * luminance at the borders of the bitmap + * @return the average luminance of all the pixels at the borders of the bitmap + */ + public static float getAvgBorderLuma(GraphicBuffer graphicBuffer, ColorSpace colorSpace) { + Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(graphicBuffer, colorSpace); + if (hwBitmap == null) { + return 0; + } + + Bitmap swaBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false); + float totalLuma = 0; + int height = swaBitmap.getHeight(); + int width = swaBitmap.getWidth(); + int i; + for (i = 0; i < width; i++) { + totalLuma += swaBitmap.getColor(i, 0).luminance(); + totalLuma += swaBitmap.getColor(i, height - 1).luminance(); + } + for (i = 0; i < height; i++) { + totalLuma += swaBitmap.getColor(0, i).luminance(); + totalLuma += swaBitmap.getColor(width - 1, i).luminance(); + } + return totalLuma / (2 * width + 2 * height); + } + + /** + * Gets the average border luma by taking a screenshot of the {@param surfaceControl}. + * @see #getAvgBorderLuma(GraphicBuffer, ColorSpace) + */ + public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) { + if (surfaceControl == null) { + return 0; + } + + Point size = new Point(); + display.getSize(size); + Rect crop = new Rect(0, 0, size.x, size.y); + SurfaceControl.ScreenshotGraphicBuffer buffer = + SurfaceControl.captureLayers(surfaceControl, crop, 1); + if (buffer == null) { + return 0; + } + + return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(), + buffer.getColorSpace()); + } + + public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) { + switch (rotation) { + case Surface.ROTATION_0: + outMatrix.reset(); + break; + case Surface.ROTATION_90: + outMatrix.setRotate(90, 0, 0); + outMatrix.postTranslate(height, 0); + break; + case Surface.ROTATION_180: + outMatrix.setRotate(180, 0, 0); + outMatrix.postTranslate(width, height); + break; + case Surface.ROTATION_270: + outMatrix.setRotate(270, 0, 0); + outMatrix.postTranslate(0, width); + break; + } + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ac85932e92f0..e939d84292b5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -10156,10 +10156,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { UserHandle.myUserId(), ACTION_PROVISION_MANAGED_USER).toArray( new String[0]); } - UserInfo userInfo = mUserManagerInternal.createUserEvenWhenDisallowed(name, - userType, userInfoFlags, disallowedPackages); - if (userInfo != null) { - user = userInfo.getUserHandle(); + try { + UserInfo userInfo = mUserManagerInternal.createUserEvenWhenDisallowed(name, + userType, userInfoFlags, disallowedPackages); + if (userInfo != null) { + user = userInfo.getUserHandle(); + } + } catch (UserManager.CheckedUserOperationException e) { + Log.e(LOG_TAG, "Couldn't createUserEvenWhenDisallowed", e); } } finally { mInjector.binderRestoreCallingIdentity(id); @@ -11349,6 +11353,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void setLockdownAdminConfiguredNetworks(ComponentName who, boolean lockdown) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(who); + + mInjector.binderWithCleanCallingIdentity(() -> + mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, + lockdown ? 1 : 0)); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS) + .setAdmin(who) + .setBoolean(lockdown) + .write(); + } + + @Override + public boolean isLockdownAdminConfiguredNetworks(ComponentName who) { + if (!mHasFeature) { + return false; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(who); + + return mInjector.binderWithCleanCallingIdentity(() -> + mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0); + } + + @Override public void setLocationEnabled(ComponentName who, boolean locationEnabled) { Objects.requireNonNull(who, "ComponentName is null"); enforceDeviceOwner(who); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 66d140767773..0b7c359cd532 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1769,7 +1769,7 @@ public final class SystemServer { if (!isWatch && !disableNetworkTime) { t.traceBegin("StartNetworkTimeUpdateService"); try { - networkTimeUpdater = new NetworkTimeUpdateServiceImpl(context); + networkTimeUpdater = new NetworkTimeUpdateService(context); ServiceManager.addService("network_time_update_service", networkTimeUpdater); } catch (Throwable e) { reportWtf("starting NetworkTimeUpdate service", e); diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 2510b60955d3..ec56e1ebc8e0 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -131,6 +131,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -2339,6 +2340,85 @@ public class KeyValueBackupTaskTest { assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(0L); } + /** Do not inform transport of an empty backup if the app hasn't backed up before */ + @Test + public void testRunTask_whenNoDataToBackupOnFirstBackup_doesNotTellTransportOfBackup() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + mBackupManagerService.setCurrentToken(0L); + when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L); + setUpAgent(PACKAGE_1); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1); + + runTask(task); + + verify(transportMock.transport, never()) + .performBackup( + argThat(packageInfo(PACKAGE_1)), any(ParcelFileDescriptor.class), anyInt()); + } + + /** Let the transport know if there are no changes for a KV backed-up package. */ + @Test + public void testRunTask_whenBackupHasCompletedAndThenNoDataChanges_transportGetsNotified() + throws Exception { + TransportMock transportMock = setUpInitializedTransport(mTransport); + when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L); + when(transportMock.transport.isAppEligibleForBackup( + argThat(packageInfo(PACKAGE_1)), eq(false))) + .thenReturn(true); + when(transportMock.transport.isAppEligibleForBackup( + argThat(packageInfo(PACKAGE_2)), eq(false))) + .thenReturn(true); + setUpAgentWithData(PACKAGE_1); + setUpAgentWithData(PACKAGE_2); + + PackageInfo endSentinel = new PackageInfo(); + endSentinel.packageName = KeyValueBackupTask.NO_DATA_END_SENTINEL; + + // Perform First Backup run, which should backup both packages + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2); + runTask(task); + InOrder order = Mockito.inOrder(transportMock.transport); + order.verify(transportMock.transport) + .performBackup( + argThat(packageInfo(PACKAGE_1)), + any(), + eq(BackupTransport.FLAG_NON_INCREMENTAL)); + order.verify(transportMock.transport).finishBackup(); + order.verify(transportMock.transport) + .performBackup( + argThat(packageInfo(PACKAGE_2)), + any(), + eq(BackupTransport.FLAG_NON_INCREMENTAL)); + order.verify(transportMock.transport).finishBackup(); + + // Run again with new data for package 1, but nothing new for package 2 + task = createKeyValueBackupTask(transportMock, PACKAGE_1); + runTask(task); + + // Now for the second run we performed one incremental backup (package 1) and + // made one "no change" call (package 2) before sending the end sentinel. + order.verify(transportMock.transport) + .performBackup( + argThat(packageInfo(PACKAGE_1)), + any(), + eq(BackupTransport.FLAG_INCREMENTAL)); + order.verify(transportMock.transport).finishBackup(); + order.verify(transportMock.transport) + .performBackup( + argThat(packageInfo(PACKAGE_2)), + any(), + eq(BackupTransport.FLAG_DATA_NOT_CHANGED)); + order.verify(transportMock.transport).finishBackup(); + order.verify(transportMock.transport) + .performBackup( + argThat(packageInfo(endSentinel)), + any(), + eq(BackupTransport.FLAG_DATA_NOT_CHANGED)); + order.verify(transportMock.transport).finishBackup(); + order.verifyNoMoreInteractions(); + } + private void runTask(KeyValueBackupTask task) { // Pretend we are not on the main-thread to prevent RemoteCall from complaining mShadowMainLooper.setCurrentThread(false); @@ -2576,6 +2656,20 @@ public class KeyValueBackupTaskTest { packageInfo != null && packageData.packageName.equals(packageInfo.packageName); } + /** Matches {@link PackageInfo} whose package name is {@code packageData.packageName}. */ + private static ArgumentMatcher<PackageInfo> packageInfo(PackageInfo packageData) { + // We have to test for packageInfo nulity because of Mockito's own stubbing with argThat(). + // E.g. if you do: + // + // 1. when(object.method(argThat(str -> str.equals("foo")))).thenReturn(0) + // 2. when(object.method(argThat(str -> str.equals("bar")))).thenReturn(2) + // + // The second line will throw NPE because it will call lambda 1 with null, since argThat() + // returns null. So we guard against that by checking for null. + return packageInfo -> + packageInfo != null && packageInfo.packageName.equals(packageInfo.packageName); + } + /** Matches {@link ApplicationInfo} whose package name is {@code packageData.packageName}. */ private static ArgumentMatcher<ApplicationInfo> applicationInfo(PackageData packageData) { return applicationInfo -> diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 3a07a692ecdd..710e8dfe6aa2 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -61,6 +61,7 @@ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.STORAGE_INTERNAL" /> <uses-permission android:name="android.permission.WATCH_APPOPS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.SUSPEND_APPS"/> @@ -71,6 +72,8 @@ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <uses-permission android:name="android.permission.DUMP" /> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java new file mode 100644 index 000000000000..ed74947bdf29 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.ActivityManagerInternal; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.os.Binder; +import android.os.Handler; +import android.os.Looper; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; +import android.util.ArrayMap; +import android.util.ArraySet; + +import androidx.test.InstrumentationRegistry; + +import com.android.server.wm.ActivityTaskManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.io.BufferedReader; +import java.io.CharArrayWriter; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringReader; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class PinnerServiceTest { + private static final int KEY_CAMERA = 0; + private static final int KEY_HOME = 1; + private static final int KEY_ASSISTANT = 2; + + private static final long WAIT_FOR_PINNER_TIMEOUT = TimeUnit.SECONDS.toMillis(2); + + @Rule + public TestableContext mContext = + new TestableContext(InstrumentationRegistry.getContext(), null); + + private final ArraySet<String> mUpdatedPackages = new ArraySet<>(); + private ResolveInfo mHomePackageResolveInfo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + + ActivityTaskManagerInternal mockActivityTaskManagerInternal = mock( + ActivityTaskManagerInternal.class); + Intent homeIntent = getHomeIntent(); + + doReturn(homeIntent).when(mockActivityTaskManagerInternal).getHomeIntent(); + LocalServices.addService(ActivityTaskManagerInternal.class, + mockActivityTaskManagerInternal); + + ActivityManagerInternal mockActivityManagerInternal = mock(ActivityManagerInternal.class); + doReturn(true).when(mockActivityManagerInternal).isUidActive(anyInt()); + LocalServices.addService(ActivityManagerInternal.class, mockActivityManagerInternal); + + mContext = spy(mContext); + + // Get HOME (Launcher) package + mHomePackageResolveInfo = mContext.getPackageManager().resolveActivityAsUser(homeIntent, + PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0); + mUpdatedPackages.add(mHomePackageResolveInfo.activityInfo.applicationInfo.packageName); + } + + @After + public void tearDown() { + Mockito.framework().clearInlineMocks(); + } + + private Intent getHomeIntent() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.addCategory(Intent.CATEGORY_DEFAULT); + return intent; + } + + private void unpinAll(PinnerService pinnerService) throws Exception { + // unpin all packages + Method unpinAppMethod = PinnerService.class.getDeclaredMethod("unpinApp", int.class); + unpinAppMethod.setAccessible(true); + unpinAppMethod.invoke(pinnerService, KEY_HOME); + unpinAppMethod.invoke(pinnerService, KEY_CAMERA); + unpinAppMethod.invoke(pinnerService, KEY_ASSISTANT); + } + + private void waitForPinnerService(PinnerService pinnerService) + throws NoSuchFieldException, IllegalAccessException { + // There's no notification/callback when pinning finished + // Block until pinner handler is done pinning and runs this empty runnable + Field pinnerHandlerField = PinnerService.class.getDeclaredField("mPinnerHandler"); + pinnerHandlerField.setAccessible(true); + Handler pinnerServiceHandler = (Handler) pinnerHandlerField.get(pinnerService); + pinnerServiceHandler.runWithScissors(() -> { + }, WAIT_FOR_PINNER_TIMEOUT); + } + + private ArraySet<Integer> getPinKeys(PinnerService pinnerService) + throws NoSuchFieldException, IllegalAccessException { + Field pinKeysArrayField = PinnerService.class.getDeclaredField("mPinKeys"); + pinKeysArrayField.setAccessible(true); + return (ArraySet<Integer>) pinKeysArrayField.get(pinnerService); + } + + private ArrayMap<Integer, Object> getPinnedApps(PinnerService pinnerService) + throws NoSuchFieldException, IllegalAccessException { + Field pinnedAppsField = PinnerService.class.getDeclaredField("mPinnedApps"); + pinnedAppsField.setAccessible(true); + return (ArrayMap<Integer, Object>) pinnedAppsField.get( + pinnerService); + } + + private String getPinnerServiceDump(PinnerService pinnerService) throws Exception { + Class<?> innerClass = Class.forName(PinnerService.class.getName() + "$BinderService"); + Constructor<?> ctor = innerClass.getDeclaredConstructor(PinnerService.class); + ctor.setAccessible(true); + Binder innerInstance = (Binder) ctor.newInstance(pinnerService); + CharArrayWriter cw = new CharArrayWriter(); + PrintWriter pw = new PrintWriter(cw, true); + Method dumpMethod = Binder.class.getDeclaredMethod("dump", FileDescriptor.class, + PrintWriter.class, String[].class); + dumpMethod.setAccessible(true); + dumpMethod.invoke(innerInstance, null, pw, null); + return cw.toString(); + } + + private int getPinnedSize(PinnerService pinnerService) throws Exception { + final String totalSizeToken = "Total size: "; + String dumpOutput = getPinnerServiceDump(pinnerService); + BufferedReader bufReader = new BufferedReader(new StringReader(dumpOutput)); + Optional<Integer> size = bufReader.lines().filter(s -> s.contains(totalSizeToken)) + .map(s -> Integer.valueOf(s.substring(totalSizeToken.length()))).findAny(); + return size.orElse(-1); + } + + @Test + public void testPinHomeApp() throws Exception { + // Enable HOME app pinning + Resources res = mock(Resources.class); + doReturn(true).when(res).getBoolean(com.android.internal.R.bool.config_pinnerHomeApp); + when(mContext.getResources()).thenReturn(res); + PinnerService pinnerService = new PinnerService(mContext); + + ArraySet<Integer> pinKeys = getPinKeys(pinnerService); + assertThat(pinKeys.valueAt(0)).isEqualTo(KEY_HOME); + + pinnerService.update(mUpdatedPackages, true); + + waitForPinnerService(pinnerService); + + ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); + assertThat(pinnedApps.get(KEY_HOME)).isNotNull(); + + // Check if dump() reports total pinned bytes + int totalPinnedSizeBytes = getPinnedSize(pinnerService); + assertThat(totalPinnedSizeBytes).isGreaterThan(0); + + // Make sure pinned files are unmapped + unpinAll(pinnerService); + } + + @Test + public void testPinHomeAppOnBootCompleted() throws Exception { + // Enable HOME app pinning + Resources res = mock(Resources.class); + doReturn(true).when(res).getBoolean(com.android.internal.R.bool.config_pinnerHomeApp); + when(mContext.getResources()).thenReturn(res); + PinnerService pinnerService = new PinnerService(mContext); + + ArraySet<Integer> pinKeys = getPinKeys(pinnerService); + assertThat(pinKeys.valueAt(0)).isEqualTo(KEY_HOME); + + pinnerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + + waitForPinnerService(pinnerService); + + ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); + assertThat(pinnedApps.get(KEY_HOME)).isNotNull(); + + // Check if dump() reports total pinned bytes + int totalPinnedSizeBytes = getPinnedSize(pinnerService); + assertThat(totalPinnedSizeBytes).isGreaterThan(0); + + // Make sure pinned files are unmapped + unpinAll(pinnerService); + } + + @Test + public void testNothingToPin() throws Exception { + // No package enabled for pinning + Resources res = mock(Resources.class); + when(mContext.getResources()).thenReturn(res); + PinnerService pinnerService = new PinnerService(mContext); + + ArraySet<Integer> pinKeys = getPinKeys(pinnerService); + assertThat(pinKeys).isEmpty(); + + pinnerService.update(mUpdatedPackages, true); + + waitForPinnerService(pinnerService); + + ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); + assertThat(pinnedApps).isEmpty(); + + // Check if dump() reports total pinned bytes + int totalPinnedSizeBytes = getPinnedSize(pinnerService); + assertThat(totalPinnedSizeBytes).isEqualTo(0); + + // Make sure pinned files are unmapped + unpinAll(pinnerService); + } + +} 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 e70cd60c9e19..aeba488b5f63 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3723,6 +3723,39 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals(-1, dpm.getLastSecurityLogRetrievalTime()); } + public void testSetLockdownAdminConfiguredNetworksWithDO() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + dpm.setLockdownAdminConfiguredNetworks(admin1, true); + verify(getServices().settings).settingsGlobalPutInt( + Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1); + + dpm.setLockdownAdminConfiguredNetworks(admin1, false); + verify(getServices().settings).settingsGlobalPutInt( + Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0); + } + + public void testSetLockdownAdminConfiguredNetworksWithPO() throws Exception { + setupProfileOwner(); + assertExpectException(SecurityException.class, null, + () -> dpm.setLockdownAdminConfiguredNetworks(admin1, false)); + verify(getServices().settings, never()).settingsGlobalPutInt( + Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0); + } + + public void testSetLockdownAdminConfiguredNetworksWithPOOfOrganizationOwnedDevice() + throws Exception { + setupProfileOwner(); + configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE); + dpm.setLockdownAdminConfiguredNetworks(admin1, true); + verify(getServices().settings).settingsGlobalPutInt( + Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1); + + dpm.setLockdownAdminConfiguredNetworks(admin1, false); + verify(getServices().settings).settingsGlobalPutInt( + Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0); + } + public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); diff --git a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java index 63bccfa01413..9ecba59912e5 100644 --- a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java +++ b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java @@ -22,6 +22,7 @@ import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; import android.test.AndroidTestCase; + import com.android.server.job.MockPriorityJobService.TestEnvironment; import com.android.server.job.MockPriorityJobService.TestEnvironment.Event; @@ -35,6 +36,11 @@ public class PrioritySchedulingTest extends AndroidTestCase { static ComponentName kJobServiceComponent; JobScheduler mJobScheduler; + // The system overrides the test app priority to be a minimum of FOREGROUND_SERVICE. We can + // bypass that override by using a priority of at least bound foreground service. + private static final int HIGH_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE + 1; + private static final int LOW_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE; + @Override public void setUp() throws Exception { super.setUp(); @@ -51,32 +57,25 @@ public class PrioritySchedulingTest extends AndroidTestCase { } public void testLowerPriorityJobPreempted() throws Exception { - JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent) - .setPriority(1) - .setOverrideDeadline(7000L) - .build(); - JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent) - .setPriority(1) - .setOverrideDeadline(7000L) - .build(); - JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent) - .setPriority(1) - .setOverrideDeadline(7000L) - .build(); - JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent) - .setPriority(2) - .setMinimumLatency(2000L) - .setOverrideDeadline(7000L) + for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) { + JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent) + .setPriority(LOW_PRIORITY) + .setOverrideDeadline(0) + .build(); + mJobScheduler.schedule(job); + } + final int higherPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; + JobInfo jobHigher = new JobInfo.Builder(higherPriorityJobId, kJobServiceComponent) + .setPriority(HIGH_PRIORITY) + .setMinimumLatency(2000) + .setOverrideDeadline(4000) .build(); - mJobScheduler.schedule(job1); - mJobScheduler.schedule(job2); - mJobScheduler.schedule(job3); - mJobScheduler.schedule(job4); - Thread.sleep(10000); // Wait for job 4 to preempt one of the lower priority jobs + mJobScheduler.schedule(jobHigher); + Thread.sleep(10000); // Wait for jobHigher to preempt one of the lower priority jobs - Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444); + Event jobHigherExecution = new Event(TestEnvironment.EVENT_START_JOB, higherPriorityJobId); ArrayList<Event> executedEvents = kTestEnvironment.getExecutedEvents(); - boolean wasJob4Executed = executedEvents.contains(job4Execution); + boolean wasJobHigherExecuted = executedEvents.contains(jobHigherExecution); boolean wasSomeJobPreempted = false; for (Event event: executedEvents) { if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) { @@ -85,35 +84,28 @@ public class PrioritySchedulingTest extends AndroidTestCase { } } assertTrue("No job was preempted.", wasSomeJobPreempted); - assertTrue("Lower priority jobs were not preempted.", wasJob4Executed); + assertTrue("Lower priority jobs were not preempted.", wasJobHigherExecuted); } public void testHigherPriorityJobNotPreempted() throws Exception { - JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent) - .setPriority(2) - .setOverrideDeadline(7000L) - .build(); - JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent) - .setPriority(2) - .setOverrideDeadline(7000L) - .build(); - JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent) - .setPriority(2) - .setOverrideDeadline(7000L) - .build(); - JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent) - .setPriority(1) - .setMinimumLatency(2000L) - .setOverrideDeadline(7000L) + for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) { + JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent) + .setPriority(HIGH_PRIORITY) + .setOverrideDeadline(0) + .build(); + mJobScheduler.schedule(job); + } + final int lowerPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; + JobInfo jobLower = new JobInfo.Builder(lowerPriorityJobId, kJobServiceComponent) + .setPriority(LOW_PRIORITY) + .setMinimumLatency(2000) + .setOverrideDeadline(3000) .build(); - mJobScheduler.schedule(job1); - mJobScheduler.schedule(job2); - mJobScheduler.schedule(job3); - mJobScheduler.schedule(job4); - Thread.sleep(10000); // Wait for job 4 to preempt one of the higher priority jobs + mJobScheduler.schedule(jobLower); + Thread.sleep(10000); - Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444); - boolean wasJob4Executed = kTestEnvironment.getExecutedEvents().contains(job4Execution); - assertFalse("Higher priority job was preempted.", wasJob4Executed); + Event jobLowerExecution = new Event(TestEnvironment.EVENT_START_JOB, lowerPriorityJobId); + boolean wasLowerExecuted = kTestEnvironment.getExecutedEvents().contains(jobLowerExecution); + assertFalse("Higher priority job was preempted.", wasLowerExecuted); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java index e375aef3b7f0..9eaf8b645c37 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java @@ -23,7 +23,9 @@ import static org.junit.Assert.assertTrue; import android.content.pm.UserInfo; import android.os.Looper; +import android.os.ServiceSpecificException; import android.os.UserHandle; +import android.os.UserManager; import android.os.UserManagerInternal; import androidx.test.InstrumentationRegistry; @@ -31,11 +33,13 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; +import com.android.server.storage.DeviceStorageMonitorInternal; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import java.util.List; @@ -243,6 +247,65 @@ public class UserManagerServiceCreateProfileTest { true /* allow remove */)); } + @Test + public void testCreateProfileForUser_lowStorageException() { + DeviceStorageMonitorInternal dsmMock = Mockito.mock(DeviceStorageMonitorInternal.class); + Mockito.when(dsmMock.isMemoryLow()).thenReturn(true); + LocalServices.addService(DeviceStorageMonitorInternal.class, dsmMock); + + try { + mUserManagerService.createProfileForUserWithThrow("user2", USER_TYPE_PROFILE_MANAGED, 0, + UserHandle.USER_SYSTEM, null); + } catch (ServiceSpecificException e) { + assertEquals(UserManager.USER_OPERATION_ERROR_LOW_STORAGE, + UserManager.UserOperationException.from(e).getUserOperationResult()); + } finally { + LocalServices.removeServiceForTest(DeviceStorageMonitorInternal.class); + } + } + + @Test + public void testCreateProfileForUser_unknownParentUser() { + DeviceStorageMonitorInternal dsmMock = Mockito.mock(DeviceStorageMonitorInternal.class); + Mockito.when(dsmMock.isMemoryLow()).thenReturn(false); + LocalServices.addService(DeviceStorageMonitorInternal.class, dsmMock); + + try { + final int badParentUserId = 1234; + mUserManagerService.createProfileForUserWithThrow("profile", USER_TYPE_PROFILE_MANAGED, + 0, badParentUserId, null); + } catch (ServiceSpecificException e) { + assertEquals(UserManager.USER_OPERATION_ERROR_UNKNOWN, + UserManager.UserOperationException.from(e).getUserOperationResult()); + } finally { + LocalServices.removeServiceForTest(DeviceStorageMonitorInternal.class); + } + } + + @Test + public void testCreateManagedProfileForUser_maxManagedUsersException() { + DeviceStorageMonitorInternal dsmMock = Mockito.mock(DeviceStorageMonitorInternal.class); + Mockito.when(dsmMock.isMemoryLow()).thenReturn(false); + LocalServices.addService(DeviceStorageMonitorInternal.class, dsmMock); + + UserManagerService userManagerServiceSpy = Mockito.spy(mUserManagerService); + Mockito.doReturn(false).when(userManagerServiceSpy).canAddMoreManagedProfiles( + Mockito.anyInt(), Mockito.anyBoolean()); + + Mockito.doReturn(false).when(userManagerServiceSpy).canAddMoreProfilesToUser( + Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean()); + + try { + userManagerServiceSpy.createProfileForUserWithThrow("profile", + USER_TYPE_PROFILE_MANAGED, 0, UserHandle.USER_SYSTEM, null); + } catch (ServiceSpecificException e) { + assertEquals(UserManager.USER_OPERATION_ERROR_MAX_USERS, + UserManager.UserOperationException.from(e).getUserOperationResult()); + } finally { + LocalServices.removeServiceForTest(DeviceStorageMonitorInternal.class); + } + } + private void removeUsers() { List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false); for (UserInfo user: users) { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 77376f069525..2469cec5766e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -39,6 +39,7 @@ import android.provider.Settings; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; +import android.util.Slog; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -69,8 +70,10 @@ public final class UserManagerTest { // Packages which are used during tests. private static final String[] PACKAGES = new String[] { - "com.android.egg" + "com.android.egg", + "com.google.android.webview" }; + private static final String TAG = UserManagerTest.class.getSimpleName(); private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); @@ -333,6 +336,9 @@ public final class UserManagerTest { assertThat(userInfo).isNotNull(); final int userId = userInfo.id; + UserManager userManagerForUser = (UserManager) mContext.createPackageContextAsUser( + "android", 0, asHandle(userId)).getSystemService(Context.USER_SERVICE); + assertThat(mUserManager.hasBadge(userId)).isEqualTo(userTypeDetails.hasBadge()); assertThat(mUserManager.getUserIconBadgeResId(userId)) .isEqualTo(userTypeDetails.getIconBadge()); @@ -340,9 +346,11 @@ public final class UserManagerTest { .isEqualTo(userTypeDetails.getBadgePlain()); assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId)) .isEqualTo(userTypeDetails.getBadgeNoBackground()); - assertThat(mUserManager.isProfile(userId)).isEqualTo(userTypeDetails.isProfile()); assertThat(mUserManager.isUserOfType(asHandle(userId), userTypeDetails.getName())) .isTrue(); + assertThat(userManagerForUser.isProfile()).isEqualTo(userTypeDetails.isProfile()); + assertThat(userManagerForUser.isUserOfType(asHandle(userId), userTypeDetails.getName())) + .isTrue(); final int badgeIndex = userInfo.profileBadge; assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo( @@ -351,7 +359,7 @@ public final class UserManagerTest { Resources.getSystem().getString(userTypeDetails.getBadgeLabel(badgeIndex), "Test")); } - // Make sure only one managed profile can be created + // Make sure only max managed profiles can be created @MediumTest @Test public void testAddManagedProfile() throws Exception { @@ -384,6 +392,11 @@ public final class UserManagerTest { UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); // Verify that the packagesToVerify are installed by default. for (String pkg : PACKAGES) { + if (!mPackageManager.isPackageAvailable(pkg)) { + Slog.w(TAG, "Package is not available " + pkg); + continue; + } + assertWithMessage("Package should be installed in managed profile: %s", pkg) .that(isPackageInstalledForUser(pkg, userInfo1.id)).isTrue(); } @@ -393,6 +406,11 @@ public final class UserManagerTest { UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES); // Verify that the packagesToVerify are not installed by default. for (String pkg : PACKAGES) { + if (!mPackageManager.isPackageAvailable(pkg)) { + Slog.w(TAG, "Package is not available " + pkg); + continue; + } + assertWithMessage( "Package should not be installed in managed profile when disallowed: %s", pkg) .that(isPackageInstalledForUser(pkg, userInfo2.id)).isFalse(); @@ -410,12 +428,22 @@ public final class UserManagerTest { UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES); // Verify that the packagesToVerify are not installed by default. for (String pkg : PACKAGES) { + if (!mPackageManager.isPackageAvailable(pkg)) { + Slog.w(TAG, "Package is not available " + pkg); + continue; + } + assertWithMessage("Pkg should not be installed in managed profile when disallowed: %s", pkg).that(isPackageInstalledForUser(pkg, userInfo.id)).isFalse(); } // Verify that the disallowed packages during profile creation can be installed now. for (String pkg : PACKAGES) { + if (!mPackageManager.isPackageAvailable(pkg)) { + Slog.w(TAG, "Package is not available " + pkg); + continue; + } + assertWithMessage("Package could not be installed: %s", pkg) .that(mPackageManager.installExistingPackageAsUser(pkg, userInfo.id)) .isEqualTo(PackageManager.INSTALL_SUCCEEDED); @@ -774,6 +802,78 @@ public final class UserManagerTest { assertThat(found).isTrue(); } + @Test + public void testCreateProfile_withContextUserId() throws Exception { + final int primaryUserId = mUserManager.getPrimaryUser().id; + + UserInfo userProfile = createProfileForUser("Managed 1", + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); + assertThat(userProfile).isNotNull(); + + UserManager um = (UserManager) mContext.createPackageContextAsUser( + "android", 0, mUserManager.getPrimaryUser().getUserHandle()) + .getSystemService(Context.USER_SERVICE); + + List<UserHandle> profiles = um.getUserProfiles(false); + assertThat(profiles.size()).isEqualTo(2); + assertThat(profiles.get(0).equals(userProfile.getUserHandle()) + || profiles.get(1).equals(userProfile.getUserHandle())).isTrue(); + } + + @Test + public void testSetUserName_withContextUserId() throws Exception { + final int primaryUserId = mUserManager.getPrimaryUser().id; + + UserInfo userInfo1 = createProfileForUser("Managed 1", + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); + assertThat(userInfo1).isNotNull(); + + UserManager um = (UserManager) mContext.createPackageContextAsUser( + "android", 0, userInfo1.getUserHandle()) + .getSystemService(Context.USER_SERVICE); + + final String newName = "Managed_user 1"; + um.setUserName(newName); + + UserInfo userInfo = mUserManager.getUserInfo(userInfo1.id); + assertThat(userInfo.name).isEqualTo(newName); + + // get user name from getUserName using context.getUserId + assertThat(um.getUserName()).isEqualTo(newName); + } + + @Test + public void testGetUserName_withContextUserId() throws Exception { + final String userName = "User 2"; + UserInfo user2 = createUser(userName, 0); + assertThat(user2).isNotNull(); + + UserManager um = (UserManager) mContext.createPackageContextAsUser( + "android", 0, user2.getUserHandle()) + .getSystemService(Context.USER_SERVICE); + + assertThat(um.getUserName()).isEqualTo(userName); + } + + @Test + public void testGetUserIcon_withContextUserId() throws Exception { + final int primaryUserId = mUserManager.getPrimaryUser().id; + + UserInfo userInfo1 = createProfileForUser("Managed 1", + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); + assertThat(userInfo1).isNotNull(); + + UserManager um = (UserManager) mContext.createPackageContextAsUser( + "android", 0, userInfo1.getUserHandle()) + .getSystemService(Context.USER_SERVICE); + + final String newName = "Managed_user 1"; + um.setUserName(newName); + + UserInfo userInfo = mUserManager.getUserInfo(userInfo1.id); + assertThat(userInfo.name).isEqualTo(newName); + } + private boolean isPackageInstalledForUser(String packageName, int userId) { try { return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null; diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index 03c10f3a86f8..22046a51d059 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -16,38 +16,54 @@ package com.android.server; +import android.app.AlarmManager; import android.app.IUiModeManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; +import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import com.android.server.twilight.TwilightManager; +import com.android.server.twilight.TwilightState; import com.android.server.wm.WindowManagerInternal; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import java.util.HashSet; -import java.util.Set; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; import static android.app.UiModeManager.MODE_NIGHT_AUTO; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -66,22 +82,51 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { TwilightManager mTwilightManager; @Mock PowerManager.WakeLock mWakeLock; - private Set<BroadcastReceiver> mScreenOffRecievers; + @Mock + AlarmManager mAlarmManager; + @Mock + PowerManager mPowerManager; + @Mock + TwilightState mTwilightState; + + private BroadcastReceiver mScreenOffCallback; + private BroadcastReceiver mTimeChangedCallback; + private AlarmManager.OnAlarmListener mCustomListener; @Before public void setUp() { - mUiManagerService = new UiModeManagerService(mContext, mWindowManager, mWakeLock, - mTwilightManager, true); - mScreenOffRecievers = new HashSet<>(); + initMocks(this); + mUiManagerService = new UiModeManagerService(mContext, + mWindowManager, mAlarmManager, mPowerManager, + mWakeLock, mTwilightManager, true); mService = mUiManagerService.getService(); when(mContext.checkCallingOrSelfPermission(anyString())) .thenReturn(PackageManager.PERMISSION_GRANTED); when(mContext.getResources()).thenReturn(mResources); when(mContext.getContentResolver()).thenReturn(mContentResolver); - when(mContext.registerReceiver(any(), any())).then(inv -> { - mScreenOffRecievers.add(inv.getArgument(0)); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mTwilightManager.getLastTwilightState()).thenReturn(mTwilightState); + when(mTwilightState.isNight()).thenReturn(true); + when(mContext.registerReceiver(notNull(), notNull())).then(inv -> { + IntentFilter filter = inv.getArgument(1); + if (filter.hasAction(Intent.ACTION_TIMEZONE_CHANGED)) { + mTimeChangedCallback = inv.getArgument(0); + } + if (filter.hasAction(Intent.ACTION_SCREEN_OFF)) { + mScreenOffCallback = inv.getArgument(0); + } return null; }); + doAnswer(inv -> { + mCustomListener = inv.getArgument(3); + return null; + }).when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), + any(AlarmManager.OnAlarmListener.class), any(Handler.class)); + + doAnswer(inv -> { + mCustomListener = () -> {}; + return null; + }).when(mAlarmManager).cancel(eq(mCustomListener)); } @Test @@ -102,7 +147,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { mService.setNightMode(MODE_NIGHT_NO); } catch (SecurityException e) { /*we should ignore this update config exception*/ } given(mContext.registerReceiver(any(), any())).willThrow(SecurityException.class); - verify(mContext).unregisterReceiver(any(BroadcastReceiver.class)); + verify(mContext, atLeastOnce()).unregisterReceiver(any(BroadcastReceiver.class)); } @Test @@ -165,6 +210,132 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { assertFalse(isNightModeActivated()); } + @Test + public void customTime_darkThemeOn() throws RemoteException { + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_NO); + mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertTrue(isNightModeActivated()); + } + + @Test + public void customTime_darkThemeOff() throws RemoteException { + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_YES); + mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertFalse(isNightModeActivated()); + } + + @Test + public void customTime_darkThemeOff_afterStartEnd() throws RemoteException { + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_YES); + mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertFalse(isNightModeActivated()); + } + + @Test + public void customTime_darkThemeOn_afterStartEnd() throws RemoteException { + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_YES); + mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertFalse(isNightModeActivated()); + } + + + + @Test + public void customTime_darkThemeOn_beforeStartEnd() throws RemoteException { + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_YES); + mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.minusHours(2L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertTrue(isNightModeActivated()); + } + + @Test + public void customTime_darkThemeOff_beforeStartEnd() throws RemoteException { + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_YES); + mService.setCustomNightModeStart(now.minusHours(2L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertFalse(isNightModeActivated()); + } + + @Test + public void customTIme_customAlarmSetWhenScreenTimeChanges() throws RemoteException { + when(mPowerManager.isInteractive()).thenReturn(false); + mService.setNightMode(MODE_NIGHT_CUSTOM); + verify(mAlarmManager, times(1)) + .setExact(anyInt(), anyLong(), anyString(), any(), any()); + mTimeChangedCallback.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED)); + verify(mAlarmManager, atLeast(2)) + .setExact(anyInt(), anyLong(), anyString(), any(), any()); + } + + @Test + public void customTime_alarmSetInTheFutureWhenOn() throws RemoteException { + LocalDateTime now = LocalDateTime.now(); + when(mPowerManager.isInteractive()).thenReturn(false); + mService.setNightMode(MODE_NIGHT_YES); + mService.setCustomNightModeStart(now.toLocalTime().minusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.toLocalTime().plusHours(1L).toNanoOfDay() / 1000); + LocalDateTime next = now.plusHours(1L); + final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + mService.setNightMode(MODE_NIGHT_CUSTOM); + verify(mAlarmManager) + .setExact(anyInt(), eq(millis), anyString(), any(), any()); + } + + @Test + public void customTime_appliesImmediatelyWhenScreenOff() throws RemoteException { + when(mPowerManager.isInteractive()).thenReturn(false); + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_NO); + mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + assertTrue(isNightModeActivated()); + } + + @Test + public void customTime_appliesOnlyWhenScreenOff() throws RemoteException { + LocalTime now = LocalTime.now(); + mService.setNightMode(MODE_NIGHT_NO); + mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setNightMode(MODE_NIGHT_CUSTOM); + assertFalse(isNightModeActivated()); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertTrue(isNightModeActivated()); + } + + @Test + public void nightAuto_appliesOnlyWhenScreenOff() throws RemoteException { + when(mTwilightState.isNight()).thenReturn(true); + mService.setNightMode(MODE_NIGHT_NO); + mService.setNightMode(MODE_NIGHT_AUTO); + assertFalse(isNightModeActivated()); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + assertTrue(isNightModeActivated()); + } + private boolean isNightModeActivated() { return (mUiManagerService.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) != 0; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 9ad6986f2f90..5b5ad877081c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -186,6 +186,20 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { } @Test + public void testAddNotification_newestFirst() { + HistoricalNotification n = getHistoricalNotification(1); + HistoricalNotification n2 = getHistoricalNotification(2); + + mDataBase.addNotification(n); + + // second add should not trigger another write + mDataBase.addNotification(n2); + + assertThat(mDataBase.mBuffer.getNotificationsToWrite().get(0)).isEqualTo(n2); + assertThat(mDataBase.mBuffer.getNotificationsToWrite().get(1)).isEqualTo(n); + } + + @Test public void testReadNotificationHistory_readsAllFiles() throws Exception { for (long i = 10; i >= 5; i--) { AtomicFile af = mock(AtomicFile.class); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index c6c64c9e0f6b..bc2fbc6c16c0 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4397,7 +4397,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND); // enqueue toast -> toast should still enqueue - ((INotificationManager)mService.mService).enqueueToast(testPackage, + ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), new TestableToastCallback(), 2000, 0); assertEquals(1, mService.mToastQueue.size()); } @@ -4417,7 +4417,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_LOW); // enqueue toast -> no toasts enqueued - ((INotificationManager)mService.mService).enqueueToast(testPackage, + ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), new TestableToastCallback(), 2000, 0); assertEquals(0, mService.mToastQueue.size()); } @@ -4440,7 +4440,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE); // enqueue toast -> no toasts enqueued - ((INotificationManager)mService.mService).enqueueToast(testPackage, + ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), new TestableToastCallback(), 2000, 0); assertEquals(0, mService.mToastQueue.size()); } @@ -4463,7 +4463,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE); // enqueue toast -> system toast can still be enqueued - ((INotificationManager)mService.mService).enqueueToast(testPackage, + ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), new TestableToastCallback(), 2000, 0); assertEquals(1, mService.mToastQueue.size()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index ad63d078fa67..c60ca48f0b3d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -123,13 +123,13 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testStackCleanupOnClearingTask() { mActivity.onParentChanged(null /*newParent*/, mActivity.getTask()); - verify(mStack, times(1)).onActivityRemovedFromStack(any()); + verify(mStack, times(1)).cleanUpActivityReferences(any()); } @Test public void testStackCleanupOnActivityRemoval() { mTask.removeChild(mActivity); - verify(mStack, times(1)).onActivityRemovedFromStack(any()); + verify(mStack, times(1)).cleanUpActivityReferences(any()); } @Test @@ -141,10 +141,9 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testNoCleanupMovingActivityInSameStack() { - final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack) - .build(); + final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); mActivity.reparent(newTask, 0, null /*reason*/); - verify(mStack, times(0)).onActivityRemovedFromStack(any()); + verify(mStack, times(0)).cleanUpActivityReferences(any()); } @Test @@ -490,7 +489,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); try { - doReturn(false).when(stack).isStackTranslucent(any()); + doReturn(false).when(stack).isTranslucent(any()); assertFalse(mStack.shouldBeVisible(null /* starting */)); mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), @@ -613,8 +612,7 @@ public class ActivityRecordTests extends ActivityTestsBase { // Sending 'null' for saved state can only happen due to timeout, so previously stored saved // states should not be overridden. mActivity.setState(STOPPING, "test"); - mActivity.activityStopped(null /* savedState */, null /* persistentSavedState */, - "desc"); + mActivity.activityStopped(null /* savedState */, null /* persistentSavedState */, "desc"); assertTrue(mActivity.hasSavedState()); assertEquals(savedState, mActivity.getSavedState()); assertEquals(persistentSavedState, mActivity.getPersistentSavedState()); @@ -1013,7 +1011,9 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() { // Empty the home stack. final ActivityStack homeStack = mActivity.getDisplay().getHomeStack(); - homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); }); + homeStack.forAllTasks((t) -> { + homeStack.removeChild(t, "test"); + }, true /* traverseTopToBottom */, homeStack); mActivity.finishing = true; doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities(); spyOn(mStack); @@ -1037,7 +1037,9 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testCompleteFinishing_lastActivityAboveEmptyHomeStack() { // Empty the home stack. final ActivityStack homeStack = mActivity.getDisplay().getHomeStack(); - homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); }); + homeStack.forAllTasks((t) -> { + homeStack.removeChild(t, "test"); + }, true /* traverseTopToBottom */, homeStack); mActivity.finishing = true; spyOn(mStack); @@ -1143,7 +1145,7 @@ public class ActivityRecordTests extends ActivityTestsBase { assertNull(mActivity.app); assertNull(mActivity.getTask()); assertEquals(0, task.getChildCount()); - assertNull(task.getStack()); + assertEquals(task.getStack(), task); assertEquals(0, stack.getChildCount()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index a5157fe98daf..393d8b83cad2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -313,13 +313,13 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack // should be visible since it is always on-top. - doReturn(false).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack).isTranslucent(any()); assertFalse(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); assertTrue(fullscreenStack.shouldBeVisible(null /* starting */)); // Home stack should be visible behind a translucent fullscreen stack. - doReturn(true).when(fullscreenStack).isStackTranslucent(any()); + doReturn(true).when(fullscreenStack).isTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); } @@ -338,8 +338,8 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible if both halves of split-screen are opaque. - doReturn(false).when(splitScreenPrimary).isStackTranslucent(any()); - doReturn(false).when(splitScreenSecondary).isStackTranslucent(any()); + doReturn(false).when(splitScreenPrimary).isTranslucent(any()); + doReturn(false).when(splitScreenSecondary).isTranslucent(any()); assertFalse(homeStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -350,7 +350,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary.getVisibility(null /* starting */)); // Home stack should be visible if one of the halves of split-screen is translucent. - doReturn(true).when(splitScreenPrimary).isStackTranslucent(any()); + doReturn(true).when(splitScreenPrimary).isTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -366,7 +366,7 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // First split-screen secondary shouldn't be visible behind another opaque split-split // secondary. - doReturn(false).when(splitScreenSecondary2).isStackTranslucent(any()); + doReturn(false).when(splitScreenSecondary2).isTranslucent(any()); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(STACK_VISIBILITY_INVISIBLE, @@ -376,7 +376,7 @@ public class ActivityStackTests extends ActivityTestsBase { // First split-screen secondary should be visible behind another translucent split-screen // secondary. - doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any()); + doReturn(true).when(splitScreenSecondary2).isTranslucent(any()); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -388,7 +388,7 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. - doReturn(false).when(assistantStack).isStackTranslucent(any()); + doReturn(false).when(assistantStack).isTranslucent(any()); assertTrue(assistantStack.shouldBeVisible(null /* starting */)); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -403,7 +403,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary2.getVisibility(null /* starting */)); // Split-screen stacks should be visible behind a translucent fullscreen stack. - doReturn(true).when(assistantStack).isStackTranslucent(any()); + doReturn(true).when(assistantStack).isTranslucent(any()); assertTrue(assistantStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -418,9 +418,9 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary2.getVisibility(null /* starting */)); // Assistant stack shouldn't be visible behind translucent split-screen stack - doReturn(false).when(assistantStack).isStackTranslucent(any()); - doReturn(true).when(splitScreenPrimary).isStackTranslucent(any()); - doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any()); + doReturn(false).when(assistantStack).isTranslucent(any()); + doReturn(true).when(splitScreenPrimary).isTranslucent(any()); + doReturn(true).when(splitScreenSecondary2).isTranslucent(any()); splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen"); splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen"); assertFalse(assistantStack.shouldBeVisible(null /* starting */)); @@ -555,7 +555,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack translucentStack = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(true).when(translucentStack).isStackTranslucent(any()); + doReturn(true).when(translucentStack).isTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(translucentStack.shouldBeVisible(null /* starting */)); @@ -603,8 +603,8 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -622,8 +622,8 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(true).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(true).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -641,8 +641,8 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure we don't move the home stack if it is already on top int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -666,9 +666,9 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack2).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack1).isTranslucent(any()); + doReturn(false).when(fullscreenStack2).isTranslucent(any()); // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the // pinned stack @@ -691,9 +691,9 @@ public class ActivityStackTests extends ActivityTestsBase { mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); - doReturn(true).when(fullscreenStack2).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack1).isTranslucent(any()); + doReturn(true).when(fullscreenStack2).isTranslucent(any()); // Ensure that we move the home stack behind the bottom most non-translucent fullscreen // stack @@ -715,9 +715,9 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack2).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack1).isTranslucent(any()); + doReturn(false).when(fullscreenStack2).isTranslucent(any()); // Ensure we don't move the home stack behind itself int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -810,9 +810,9 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - doReturn(false).when(splitScreenPrimary).isStackTranslucent(any()); - doReturn(false).when(splitScreenSecondary).isStackTranslucent(any()); - doReturn(false).when(assistantStack).isStackTranslucent(any()); + doReturn(false).when(splitScreenPrimary).isTranslucent(any()); + doReturn(false).when(splitScreenSecondary).isTranslucent(any()); + doReturn(false).when(assistantStack).isTranslucent(any()); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -829,7 +829,7 @@ public class ActivityStackTests extends ActivityTestsBase { boolean translucent) { final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay, windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(translucent).when(stack).isStackTranslucent(any()); + doReturn(translucent).when(stack).isTranslucent(any()); return stack; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 4beede93aea2..eb84d0af35c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -375,7 +375,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { intent.setComponent(mComponent); intent.setFlags(mFlags); - final Task task = new Task(mSupervisor.mService, mTaskId, aInfo, + final Task task = new ActivityStack(mSupervisor.mService, mTaskId, aInfo, intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/, null /*taskDescription*/, mStack); spyOn(task); @@ -398,6 +398,8 @@ class ActivityTestsBase extends SystemServiceTestsBase { private int mActivityType = ACTIVITY_TYPE_STANDARD; private boolean mOnTop = true; private boolean mCreateActivity = true; + private ActivityInfo mInfo; + private Intent mIntent; StackBuilder(RootWindowContainer root) { mRootWindowContainer = root; @@ -434,13 +436,22 @@ class ActivityTestsBase extends SystemServiceTestsBase { return this; } + StackBuilder setActivityInfo(ActivityInfo info) { + mInfo = info; + return this; + } + + StackBuilder setIntent(Intent intent) { + mIntent = intent; + return this; + } + ActivityStack build() { final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId(); - final ActivityStack stack; + final ActivityStack stack = mDisplay.createStackUnchecked(mWindowingMode, + mActivityType, stackId, mOnTop, mInfo, mIntent); final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor; - stack = mDisplay.createStackUnchecked(mWindowingMode, mActivityType, stackId, mOnTop); - if (mCreateActivity) { new ActivityBuilder(supervisor.mService) .setCreateTask(true) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index 9562fa41a4b7..fa0485c8678b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -152,6 +152,8 @@ public class TaskRecordTests extends ActivityTestsBase { @Test public void testReturnsToHomeStack() throws Exception { final Task task = createTask(1); + spyOn(task); + doReturn(true).when(task).hasChild(); assertFalse(task.returnsToHomeStack()); task.intent = null; assertFalse(task.returnsToHomeStack()); @@ -906,7 +908,7 @@ public class TaskRecordTests extends ActivityTestsBase { } private Task createTask(int taskId) { - return new Task(mService, taskId, new Intent(), null, null, null, + return new ActivityStack(mService, taskId, new Intent(), null, null, null, ActivityBuilder.getDefaultComponent(), null, false, false, false, 0, 10050, null, 0, false, null, 0, 0, 0, 0, 0, null, 0, false, false, false, 0, 0, null /*ActivityInfo*/, null /*_voiceSession*/, null /*_voiceInteractor*/, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java index b4f5751a6ca2..6e4be88a31fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -115,7 +115,7 @@ public class TaskStackTests extends WindowTestsBase { // Remove stack and check if its child is also removed. stack.removeImmediately(); assertNull(stack.getDisplayContent()); - assertNull(task.getStack()); + assertNull(task.getParent()); } @Test @@ -131,7 +131,7 @@ public class TaskStackTests extends WindowTestsBase { assertEquals(0, stack.getChildCount()); assertNull(stack.getDisplayContent()); assertNull(task.getDisplayContent()); - assertNull(task.getStack()); + assertNull(task.getParent()); } @Test @@ -140,6 +140,7 @@ public class TaskStackTests extends WindowTestsBase { final Task task = createTaskInStack(stack, 0 /* userId */); // Stack removal is deferred if one of its child is animating. + doReturn(true).when(stack).hasWindowsAlive(); doReturn(true).when(task).isAnimating(TRANSITION | CHILDREN); stack.removeIfPossible(); diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java new file mode 100644 index 000000000000..9cda08458640 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java @@ -0,0 +1,153 @@ +/* + * 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.utils; + +import static android.graphics.Bitmap.Config.ARGB_8888; + +import static org.junit.Assert.assertEquals; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.view.Surface; + +import org.junit.Before; +import org.junit.Test; + +public class RotationAnimationUtilsTest { + + private static final int BITMAP_HEIGHT = 100; + private static final int BITMAP_WIDTH = 100; + private static final int POINT_WIDTH = 1000; + private static final int POINT_HEIGHT = 2000; + + private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3); + private Matrix mMatrix; + + @Before + public void setup() { + mMatrix = new Matrix(); + } + + @Test + public void blackLuma() { + Bitmap swBitmap = createBitmap(0); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(0, borderLuma, 0); + } + + @Test + public void whiteLuma() { + Bitmap swBitmap = createBitmap(1); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(1, borderLuma, 0); + } + + @Test + public void whiteImageBlackBorderLuma() { + Bitmap swBitmap = createBitmap(1); + setBorderLuma(swBitmap, 0); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(0, borderLuma, 0); + } + + @Test + public void blackImageWhiteBorderLuma() { + Bitmap swBitmap = createBitmap(0); + setBorderLuma(swBitmap, 1); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(1, borderLuma, 0); + } + + @Test + public void rotate_0_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(POINT_WIDTH, newPoints.x, 0); + assertEquals(POINT_HEIGHT, newPoints.y, 0); + } + + @Test + public void rotate_90_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(0, newPoints.x, 0); + assertEquals(POINT_WIDTH, newPoints.y, 0); + } + + @Test + public void rotate_180_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(0, newPoints.x, 0); + assertEquals(0, newPoints.y, 0); + } + + @Test + public void rotate_270_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(POINT_HEIGHT, newPoints.x, 0); + assertEquals(0, newPoints.y, 0); + } + + private PointF checkMappedPoints(int x, int y) { + final float[] fs = new float[] {x, y}; + mMatrix.mapPoints(fs); + return new PointF(fs[0], fs[1]); + } + + private Bitmap createBitmap(float luma) { + Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, ARGB_8888); + for (int i = 0; i < BITMAP_WIDTH; i++) { + for (int j = 0; j < BITMAP_HEIGHT; j++) { + bitmap.setPixel(i, j, Color.argb(1, luma, luma, luma)); + } + } + return bitmap; + } + + private GraphicBuffer swBitmapToGraphicsBuffer(Bitmap swBitmap) { + Bitmap hwBitmap = swBitmap.copy(Bitmap.Config.HARDWARE, false); + return hwBitmap.createGraphicBufferHandle(); + } + + private void setBorderLuma(Bitmap swBitmap, float luma) { + int i; + int width = swBitmap.getWidth(); + int height = swBitmap.getHeight(); + for (i = 0; i < width; i++) { + swBitmap.setPixel(i, 0, Color.argb(1, luma, luma, luma)); + swBitmap.setPixel(i, height - 1, Color.argb(1, luma, luma, luma)); + } + for (i = 0; i < height; i++) { + swBitmap.setPixel(0, i, Color.argb(1, luma, luma, luma)); + swBitmap.setPixel(width - 1, i, Color.argb(1, luma, luma, luma)); + } + } +} diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 275319491e39..b1bd04ecb13f 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -56,6 +56,8 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.server.FgThread; +import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import java.io.File; @@ -64,6 +66,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; /** * UsbService manages all USB related state, including both host and device support. @@ -74,6 +77,9 @@ public class UsbService extends IUsbManager.Stub { public static class Lifecycle extends SystemService { private UsbService mUsbService; + private final CompletableFuture<Void> mOnStartFinished = new CompletableFuture<>(); + private final CompletableFuture<Void> mOnActivityManagerPhaseFinished = + new CompletableFuture<>(); public Lifecycle(Context context) { super(context); @@ -81,32 +87,41 @@ public class UsbService extends IUsbManager.Stub { @Override public void onStart() { - mUsbService = new UsbService(getContext()); - publishBinderService(Context.USB_SERVICE, mUsbService); + SystemServerInitThreadPool.submit(() -> { + mUsbService = new UsbService(getContext()); + publishBinderService(Context.USB_SERVICE, mUsbService); + mOnStartFinished.complete(null); + }, "UsbService$Lifecycle#onStart"); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { - mUsbService.systemReady(); + SystemServerInitThreadPool.submit(() -> { + mOnStartFinished.join(); + mUsbService.systemReady(); + mOnActivityManagerPhaseFinished.complete(null); + }, "UsbService$Lifecycle#onBootPhase"); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { + mOnActivityManagerPhaseFinished.join(); mUsbService.bootCompleted(); } } @Override - public void onSwitchUser(int newUserId) { - mUsbService.onSwitchUser(newUserId); + public void onSwitchUser(TargetUser from, TargetUser to) { + FgThread.getHandler() + .postAtFrontOfQueue(() -> mUsbService.onSwitchUser(to.getUserIdentifier())); } @Override - public void onStopUser(int userHandle) { - mUsbService.onStopUser(UserHandle.of(userHandle)); + public void onStopUser(TargetUser userInfo) { + mUsbService.onStopUser(userInfo.getUserHandle()); } @Override - public void onUnlockUser(int userHandle) { - mUsbService.onUnlockUser(userHandle); + public void onUnlockUser(TargetUser userInfo) { + mUsbService.onUnlockUser(userInfo.getUserIdentifier()); } } diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index bc6a9e848e2a..ef11f469d9a0 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -34,6 +34,8 @@ import android.telephony.euicc.EuiccInfo; import android.telephony.euicc.EuiccManager.OtaStatus; import android.util.Log; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.LinkedBlockingQueue; @@ -583,6 +585,13 @@ public abstract class EuiccService extends Service { public abstract int onRetainSubscriptionsForFactoryReset(int slotId); /** + * Dump to a provided printWriter. + */ + public void dump(@NonNull PrintWriter printWriter) { + printWriter.println("The connected LPA does not implement EuiccService#dump()"); + } + + /** * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}. */ private class IEuiccServiceWrapper extends IEuiccService.Stub { @@ -834,5 +843,22 @@ public abstract class EuiccService extends Service { } }); } + + @Override + public void dump(IEuiccServiceDumpResultCallback callback) throws RemoteException { + mExecutor.execute(new Runnable() { + @Override + public void run() { + try { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + EuiccService.this.dump(pw); + callback.onComplete(sw.toString()); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); + } } } diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl index 2acc47aae919..bb7b569f17f9 100644 --- a/telephony/java/android/service/euicc/IEuiccService.aidl +++ b/telephony/java/android/service/euicc/IEuiccService.aidl @@ -29,6 +29,7 @@ import android.service.euicc.IOtaStatusChangedCallback; import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; +import android.service.euicc.IEuiccServiceDumpResultCallback; import android.telephony.euicc.DownloadableSubscription; import android.os.Bundle; @@ -56,4 +57,5 @@ oneway interface IEuiccService { int slotIndex, int options, in IEraseSubscriptionsCallback callback); void retainSubscriptionsForFactoryReset( int slotId, in IRetainSubscriptionsForFactoryResetCallback callback); + void dump(in IEuiccServiceDumpResultCallback callback); }
\ No newline at end of file diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c9d5006946aa..2c21a609406c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -45,7 +45,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkStats; @@ -11103,8 +11102,8 @@ public class TelephonyManager { */ public boolean isDataCapable() { if (mContext == null) return true; - return mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_TELEPHONY_DATA); + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_mobile_data_capable); } /** diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 6e630e3a7024..789632082758 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -305,42 +305,44 @@ public class ApnSetting implements Parcelable { private static final Map<Integer, String> MVNO_TYPE_INT_MAP; static { - APN_TYPE_STRING_MAP = new ArrayMap<String, Integer>(); - APN_TYPE_STRING_MAP.put("*", TYPE_ALL); - APN_TYPE_STRING_MAP.put("default", TYPE_DEFAULT); - APN_TYPE_STRING_MAP.put("mms", TYPE_MMS); - APN_TYPE_STRING_MAP.put("supl", TYPE_SUPL); - APN_TYPE_STRING_MAP.put("dun", TYPE_DUN); - APN_TYPE_STRING_MAP.put("hipri", TYPE_HIPRI); - APN_TYPE_STRING_MAP.put("fota", TYPE_FOTA); - APN_TYPE_STRING_MAP.put("ims", TYPE_IMS); - APN_TYPE_STRING_MAP.put("cbs", TYPE_CBS); - APN_TYPE_STRING_MAP.put("ia", TYPE_IA); - APN_TYPE_STRING_MAP.put("emergency", TYPE_EMERGENCY); - APN_TYPE_STRING_MAP.put("mcx", TYPE_MCX); - APN_TYPE_STRING_MAP.put("xcap", TYPE_XCAP); - APN_TYPE_INT_MAP = new ArrayMap<Integer, String>(); - APN_TYPE_INT_MAP.put(TYPE_DEFAULT, "default"); - APN_TYPE_INT_MAP.put(TYPE_MMS, "mms"); - APN_TYPE_INT_MAP.put(TYPE_SUPL, "supl"); - APN_TYPE_INT_MAP.put(TYPE_DUN, "dun"); - APN_TYPE_INT_MAP.put(TYPE_HIPRI, "hipri"); - APN_TYPE_INT_MAP.put(TYPE_FOTA, "fota"); - APN_TYPE_INT_MAP.put(TYPE_IMS, "ims"); - APN_TYPE_INT_MAP.put(TYPE_CBS, "cbs"); - APN_TYPE_INT_MAP.put(TYPE_IA, "ia"); - APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, "emergency"); - APN_TYPE_INT_MAP.put(TYPE_MCX, "mcx"); - APN_TYPE_INT_MAP.put(TYPE_XCAP, "xcap"); - - PROTOCOL_STRING_MAP = new ArrayMap<String, Integer>(); + APN_TYPE_STRING_MAP = new ArrayMap<>(); + APN_TYPE_STRING_MAP.put(TYPE_ALL_STRING, TYPE_ALL); + APN_TYPE_STRING_MAP.put(TYPE_DEFAULT_STRING, TYPE_DEFAULT); + APN_TYPE_STRING_MAP.put(TYPE_MMS_STRING, TYPE_MMS); + APN_TYPE_STRING_MAP.put(TYPE_SUPL_STRING, TYPE_SUPL); + APN_TYPE_STRING_MAP.put(TYPE_DUN_STRING, TYPE_DUN); + APN_TYPE_STRING_MAP.put(TYPE_HIPRI_STRING, TYPE_HIPRI); + APN_TYPE_STRING_MAP.put(TYPE_FOTA_STRING, TYPE_FOTA); + APN_TYPE_STRING_MAP.put(TYPE_IMS_STRING, TYPE_IMS); + APN_TYPE_STRING_MAP.put(TYPE_CBS_STRING, TYPE_CBS); + APN_TYPE_STRING_MAP.put(TYPE_IA_STRING, TYPE_IA); + APN_TYPE_STRING_MAP.put(TYPE_EMERGENCY_STRING, TYPE_EMERGENCY); + APN_TYPE_STRING_MAP.put(TYPE_MCX_STRING, TYPE_MCX); + APN_TYPE_STRING_MAP.put(TYPE_XCAP_STRING, TYPE_XCAP); + + APN_TYPE_INT_MAP = new ArrayMap<>(); + APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING); + APN_TYPE_INT_MAP.put(TYPE_MMS, TYPE_MMS_STRING); + APN_TYPE_INT_MAP.put(TYPE_SUPL, TYPE_SUPL_STRING); + APN_TYPE_INT_MAP.put(TYPE_DUN, TYPE_DUN_STRING); + APN_TYPE_INT_MAP.put(TYPE_HIPRI, TYPE_HIPRI_STRING); + APN_TYPE_INT_MAP.put(TYPE_FOTA, TYPE_FOTA_STRING); + APN_TYPE_INT_MAP.put(TYPE_IMS, TYPE_IMS_STRING); + APN_TYPE_INT_MAP.put(TYPE_CBS, TYPE_CBS_STRING); + APN_TYPE_INT_MAP.put(TYPE_IA, TYPE_IA_STRING); + APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, TYPE_EMERGENCY_STRING); + APN_TYPE_INT_MAP.put(TYPE_MCX, TYPE_MCX_STRING); + APN_TYPE_INT_MAP.put(TYPE_XCAP, TYPE_XCAP_STRING); + + PROTOCOL_STRING_MAP = new ArrayMap<>(); PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP); PROTOCOL_STRING_MAP.put("IPV6", PROTOCOL_IPV6); PROTOCOL_STRING_MAP.put("IPV4V6", PROTOCOL_IPV4V6); PROTOCOL_STRING_MAP.put("PPP", PROTOCOL_PPP); PROTOCOL_STRING_MAP.put("NON-IP", PROTOCOL_NON_IP); PROTOCOL_STRING_MAP.put("UNSTRUCTURED", PROTOCOL_UNSTRUCTURED); - PROTOCOL_INT_MAP = new ArrayMap<Integer, String>(); + + PROTOCOL_INT_MAP = new ArrayMap<>(); PROTOCOL_INT_MAP.put(PROTOCOL_IP, "IP"); PROTOCOL_INT_MAP.put(PROTOCOL_IPV6, "IPV6"); PROTOCOL_INT_MAP.put(PROTOCOL_IPV4V6, "IPV4V6"); @@ -348,12 +350,13 @@ public class ApnSetting implements Parcelable { PROTOCOL_INT_MAP.put(PROTOCOL_NON_IP, "NON-IP"); PROTOCOL_INT_MAP.put(PROTOCOL_UNSTRUCTURED, "UNSTRUCTURED"); - MVNO_TYPE_STRING_MAP = new ArrayMap<String, Integer>(); + MVNO_TYPE_STRING_MAP = new ArrayMap<>(); MVNO_TYPE_STRING_MAP.put("spn", MVNO_TYPE_SPN); MVNO_TYPE_STRING_MAP.put("imsi", MVNO_TYPE_IMSI); MVNO_TYPE_STRING_MAP.put("gid", MVNO_TYPE_GID); MVNO_TYPE_STRING_MAP.put("iccid", MVNO_TYPE_ICCID); - MVNO_TYPE_INT_MAP = new ArrayMap<Integer, String>(); + + MVNO_TYPE_INT_MAP = new ArrayMap<>(); MVNO_TYPE_INT_MAP.put(MVNO_TYPE_SPN, "spn"); MVNO_TYPE_INT_MAP.put(MVNO_TYPE_IMSI, "imsi"); MVNO_TYPE_INT_MAP.put(MVNO_TYPE_GID, "gid"); diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java index b8b2de5141a7..f3c89d8addf6 100644 --- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java @@ -97,6 +97,11 @@ public class WindowInsetsActivity extends Activity { mRoot.setWindowInsetsAnimationCallback(new WindowInsetsAnimationCallback() { @Override + public int getDispatchMode() { + return DISPATCH_MODE_STOP; + } + + @Override public void onPrepare(InsetsAnimation animation) { if ((animation.getTypeMask() & Type.ime()) != 0) { imeAnim = animation; diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt new file mode 100644 index 000000000000..d250ad3a2b12 --- /dev/null +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -0,0 +1,39 @@ +/* + * 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 + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkAgentConfigTest { + @Test + fun testParcelNetworkAgentConfig() { + val config = NetworkAgentConfig.Builder().apply { + setExplicitlySelected(true) + setLegacyType(ConnectivityManager.TYPE_ETHERNET) + setSubscriberId("MySubId") + setPartialConnectivityAcceptable(false) + setUnvalidatedConnectivityAcceptable(true) + }.build() + assertParcelSane(config, 9) + } +} diff --git a/wifi/Android.bp b/wifi/Android.bp index 5ef892d005ac..099cbff15fb4 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -58,19 +58,20 @@ test_access_hidden_api_whitelist = [ // classes before they are renamed. java_library { name: "framework-wifi-pre-jarjar", - // TODO(b/140299412) should be core_current once we build against framework-system-stubs - sdk_version: "core_platform", + // TODO(b/146757305): sdk_version should be "module_lib_current" + sdk_version: "core_current", static_libs: [ "framework-wifi-util-lib", "android.hardware.wifi-V1.0-java-constants", ], libs: [ - // TODO(b/140299412) should be framework-system-stubs once we fix all @hide dependencies - "framework-minus-apex", "framework-annotations-lib", "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage "framework-telephony-stubs", + // TODO(b/146757305): should be unnecessary once + // sdk_version="module_lib_current" + "android_system_stubs_current", ], srcs: [ ":framework-wifi-updatable-sources", @@ -80,13 +81,21 @@ java_library { "//frameworks/opt/net/wifi/service", "//frameworks/opt/net/wifi/tests/wifitests", ], + + // TODO(b/146757305): should be unnecessary once + // sdk_version="module_lib_current" + aidl: { + include_dirs: [ + "frameworks/base/core/java", + ], + }, } // post-jarjar version of framework-wifi java_library { name: "framework-wifi", - // TODO(b/140299412) should be core_current once we build against framework-system-stubs - sdk_version: "core_platform", + // TODO(b/146757305): sdk_version should be "module_lib_current" + sdk_version: "core_current", static_libs: [ "framework-wifi-pre-jarjar", ], @@ -98,7 +107,6 @@ java_library { }, hostdex: true, // for hiddenapi check visibility: [ - "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed "//frameworks/opt/net/wifi/service:__subpackages__", ] + test_access_hidden_api_whitelist, apex_available: [ diff --git a/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl b/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl index d14ec57ea07a..51d74f0fcfa9 100644 --- a/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl +++ b/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl @@ -17,7 +17,6 @@ package android.net.wifi; import android.net.wifi.INetworkRequestUserSelectionCallback; -import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; /** @@ -31,7 +30,7 @@ oneway interface INetworkRequestMatchCallback void onAbort(); - void onMatch(in List<ScanResult> scanResults); + void onMatch(in List<android.net.wifi.ScanResult> scanResults); void onUserSelectionConnectSuccess(in WifiConfiguration wificonfiguration); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index dafb4de62f3c..0b5969a8999d 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -36,7 +36,6 @@ import android.net.wifi.ISuggestionConnectionStatusListener; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.ITxPacketCountListener; import android.net.wifi.IWifiConnectedNetworkScorer; -import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; @@ -61,9 +60,9 @@ interface IWifiManager ParceledListSlice getPrivilegedConfiguredNetworks(String packageName, String featureId); - Map getAllMatchingFqdnsForScanResults(in List<ScanResult> scanResult); + Map getAllMatchingFqdnsForScanResults(in List<android.net.wifi.ScanResult> scanResult); - Map getMatchingOsuProviders(in List<ScanResult> scanResult); + Map getMatchingOsuProviders(in List<android.net.wifi.ScanResult> scanResult); Map getMatchingPasspointConfigsForOsuProviders(in List<OsuProvider> osuProviders); @@ -97,7 +96,7 @@ interface IWifiManager boolean startScan(String packageName, String featureId); - List<ScanResult> getScanResults(String callingPackage, String callingFeatureId); + List<android.net.wifi.ScanResult> getScanResults(String callingPackage, String callingFeatureId); boolean disconnect(String packageName); @@ -256,7 +255,7 @@ interface IWifiManager int calculateSignalLevel(int rssi); - List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults); + List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<android.net.wifi.ScanResult> scanResults); boolean setWifiConnectedNetworkScorer(in IBinder binder, in IWifiConnectedNetworkScorer scorer); diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index c02f8c3a8cd1..c8fd2434e64f 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -212,7 +212,6 @@ public final class SoftApConfiguration implements Parcelable { */ public static final int SECURITY_TYPE_OPEN = 0; - /** * The definition of security type WPA2-PSK. */ @@ -408,7 +407,11 @@ public final class SoftApConfiguration implements Parcelable { /** * Get security type params which depends on which security passphrase to set. * - * @return One of the security types from {@link SecurityType}. + * @return One of: + * {@link #SECURITY_TYPE_OPEN}, + * {@link #SECURITY_TYPE_WPA2_PSK}, + * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION}, + * {@link #SECURITY_TYPE_WPA3_SAE} */ public @SecurityType int getSecurityType() { return mSecurityType; diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 0cf0c22d8dc2..6085eae252ef 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -692,12 +692,14 @@ public final class WifiNetworkSuggestion implements Parcelable { * Network configuration for the provided network. * @hide */ + @NonNull public final WifiConfiguration wifiConfiguration; /** * Passpoint configuration for the provided network. * @hide */ + @Nullable public final PasspointConfiguration passpointConfiguration; /** @@ -734,7 +736,7 @@ public final class WifiNetworkSuggestion implements Parcelable { /** @hide */ public WifiNetworkSuggestion() { - this.wifiConfiguration = null; + this.wifiConfiguration = new WifiConfiguration(); this.passpointConfiguration = null; this.isAppInteractionRequired = false; this.isUserInteractionRequired = false; @@ -842,4 +844,25 @@ public final class WifiNetworkSuggestion implements Parcelable { .append(" ]"); return sb.toString(); } + + /** + * Get the {@link WifiConfiguration} associated with this Suggestion. + * @hide + */ + @SystemApi + @NonNull + public WifiConfiguration getWifiConfiguration() { + return wifiConfiguration; + } + + /** + * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this + * Suggestion is not for a Passpoint network. + * @hide + */ + @SystemApi + @Nullable + public PasspointConfiguration getPasspointConfiguration() { + return passpointConfiguration; + } } diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 4f602fac7a36..18533ef5b117 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -291,28 +291,46 @@ public class WifiScanner { @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public final List<HiddenNetwork> hiddenNetworks = new ArrayList<>(); - /** period of background scan; in millisecond, 0 => single shot scan */ + /** + * period of background scan; in millisecond, 0 => single shot scan + * @deprecated Background scan support is removed. + */ + @Deprecated public int periodInMs; - /** must have a valid REPORT_EVENT value */ + /** + * must have a valid REPORT_EVENT value + * @deprecated Background scan support is removed. + */ + @Deprecated public int reportEvents; - /** defines number of bssids to cache from each scan */ + /** + * defines number of bssids to cache from each scan + * @deprecated Background scan support is removed. + */ + @Deprecated public int numBssidsPerScan; /** * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL * to wake up at fixed interval + * @deprecated Background scan support is removed. */ + @Deprecated public int maxScansToCache; /** * if maxPeriodInMs is non zero or different than period, then this bucket is * a truncated binary exponential backoff bucket and the scan period will grow * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount)) * to maxPeriodInMs + * @deprecated Background scan support is removed. */ + @Deprecated public int maxPeriodInMs; /** * for truncated binary exponential back off bucket, number of scans to perform * for a given period + * @deprecated Background scan support is removed. */ + @Deprecated public int stepCount; /** * Flag to indicate if the scan settings are targeted for PNO scan. @@ -788,7 +806,9 @@ public class WifiScanner { /** * Framework co-ordinates scans across multiple apps; so it may not give exactly the * same period requested. If period of a scan is changed; it is reported by this event. + * @deprecated Background scan support is removed. */ + @Deprecated public void onPeriodChanged(int periodInMs); /** * reports results retrieved from background scan and single shot scans @@ -891,7 +911,9 @@ public class WifiScanner { * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. + * @deprecated Background scan support is removed. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(ScanSettings settings, ScanListener listener, WorkSource workSource) { @@ -911,7 +933,9 @@ public class WifiScanner { * stop an ongoing wifi scan * @param listener specifies which scan to cancel; must be same object as passed in {@link * #startBackgroundScan} + * @deprecated Background scan support is removed. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopBackgroundScan(ScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); @@ -927,7 +951,9 @@ public class WifiScanner { /** * reports currently available scan results on appropriate listeners * @return true if all scan results were reported correctly + * @deprecated Background scan support is removed. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults() { validateChannel(); |